Serving ‘files’ in Vapor

In my experience, dynamically generating a file, serving it immediately, and not persisting it on the server is a pretty common use case. In general, this is one of two things – either a PDF download, or a CSV. While my Vapor tinkering hasn’t yet given me an opportunity to generate PDFs on the server, I have had an occasion to create a CSV, and wrote up a little helper for doing so.

import Vapor

struct TextFileResponse {
enum ResponseType {
case inline, attachment(filename: String)
}

var body: String
var type: ResponseType
var contentType: String
}

extension TextFileResponse: ResponseEncodable {
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
var headers = HTTPHeaders()
headers.add(name: .contentType, value: contentType)
switch type {
case .inline:
headers.add(name: .contentDisposition, value: "inline")
case .attachment(let filename):
headers.add(name: .contentDisposition, value: "attachment; filename=\"\(filename)\"")
}
return request.eventLoop.makeSucceededFuture(.init(status: .ok, headers: headers, body: .init(string: body)))
}
}

That’ll work for any file you can assemble as text; CSV just struck me as being the most useful example. Use ResponseType.inline for a file you want displayed in a browser tab, and .attachment if it’s for downloading.

And if you’re doing a lot of CSVs, give yourself a nice little helper:

extension TextFileResponse {
static func csv(body: String, name: String) -> TextFileResponse {
.init(body: body, type: .attachment(filename: name), contentType: "text/csv")
}
}