I have a working directory that contains every user's picture and I am trying to implement a call that returns data containing the user's picture, defined in this structure:
struct ImageData: Content {
var picture: Data // UIImage data
}
I tried to implement a solution also partially using what I found in the book 'Server Side Swift with Vapor' (version 3) in chapter 26 but that's different for me because I am not using Leaf and I need to return the data directly.
I came up with this function to return the user picture, which does its job but I am trying to improve it.
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<ImageData> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
// To do: throw error (flatMapThrowing?)
let filename = user.profilePicture!
let path = req.application.directory.workingDirectory
imageFolder
filename
// Improvement: Do I need this?
var data = Data()
return req.fileio.readFile(at: path) { buffer -> EventLoopFuture<Void> in
let additionalData = Data(buffer: buffer)
data.append(contentsOf: additionalData)
return req.eventLoop.makeSucceededVoidFuture()
}.map {
return ImageData(picture: data)
}
}
}
First:
- How to implement this using flatMapThrowing? If I replace flatMap with flatMapThrowing I get this error: "Cannot convert return expression of type 'EventLoopFuture' to return type 'ImageData'". Which doesn't make sense to me considering that flatMap allows returning a future and not a value.
- I didn't find any solution other than using a Data variable and appending chunks of data as more data is read. I am not sure that this is thread-safe, FIFO and I don't consider it an elegant solution. Does anybody know any better way of doing it?
CodePudding user response:
The short answer is that as soon as you have the file path, Vapor can handle it all for you:
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<Response> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.tryflatMap { user in
// To do: throw error (flatMapThrowing?)
guard let filename = user.profilePicture else {
throw Abort(.notFound)
}
let path = req.application.directory.workingDirectory
imageFolder
filename
return req.fileio.streamFile(at: path)
}
}
You can use tryFlatMap
to have a flatMap
that can throw and you want to return a Response
. Manually messing around with Data
is not usually a good idea.
However, the better answers are use async/await and the FileMiddleware as two tools to clean up your code and remove the handler altogether