Home > Enterprise >  Code improvement - how to serve use pictures in Vapor?
Code improvement - how to serve use pictures in Vapor?

Time:11-02

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

  • Related