Vapor: Saving uploaded file

Created on 29 Nov 2016  ·  15Comments  ·  Source: vapor/vapor

is there a way to save uploaded file in vapor?

i found DataFile class that conforms to DataFile but it’s method “save” looks like

public func save(bytes: Bytes, to path: String) throws {
    throw Error.unimplemented
}

Most helpful comment

To save files that are uploaded through multipart requests, you simply have to use FileManager, just like you would do on iOS.

Here is the code I use for saving files :

guard let fileData = request.multipart?["image"]?.file?.data else {
    throw Abort.custom(status: .badRequest, message: "No file in request")
}

guard let workPath = Droplet.instance?.workDir else {
    throw Abort.custom(status: .internalServerError, message: "Missing working directory")
}

let name = UUID().uuidString + ".png"
let imageFolder = "Public/images"
let saveURL = URL(fileURLWithPath: workPath).appendingPathComponent(imageFolder, isDirectory: true).appendingPathComponent(name, isDirectory: false)

do {
    let data = Data(bytes: fileData)
    try data.write(to: saveURL)
} catch {
    throw Abort.custom(status: .internalServerError, message: "Unable to write multipart form data to file. Underlying error \(error)")
}

All 15 comments

To save files that are uploaded through multipart requests, you simply have to use FileManager, just like you would do on iOS.

Here is the code I use for saving files :

guard let fileData = request.multipart?["image"]?.file?.data else {
    throw Abort.custom(status: .badRequest, message: "No file in request")
}

guard let workPath = Droplet.instance?.workDir else {
    throw Abort.custom(status: .internalServerError, message: "Missing working directory")
}

let name = UUID().uuidString + ".png"
let imageFolder = "Public/images"
let saveURL = URL(fileURLWithPath: workPath).appendingPathComponent(imageFolder, isDirectory: true).appendingPathComponent(name, isDirectory: false)

do {
    let data = Data(bytes: fileData)
    try data.write(to: saveURL)
} catch {
    throw Abort.custom(status: .internalServerError, message: "Unable to write multipart form data to file. Underlying error \(error)")
}

@hhanesand awesome! thank you very much!

Is there any reason why @hhanesand answer can't be implemented at DataFile class?

hi @hhanesand, it seems vapor 2 have some change with request.multipart. It is a [Multipart.Part] now.

I think we can also use request.formData based on the encoding.

It can be

guard let filebytes = request.formData?["image"]?.part.body else {
    throw Abort.custom(status: .badRequest, message: "No file in request")
}

or

guard let filebytes = request.multipart?[0].body else {
    throw Abort(.badRequest, metadata: "No file in request")
}

@tanner0101 ,
How to do this in vapor 3?

I don't have the instance for 'Droplet.instance?.workDir'

Now you want

let directory = DirectoryConfig.detect()
let workingDirectory = directory.workDir

in Vapor 3

@twof ,
How I can get uploaded file from the request in vapour 3?

Would be great to see a vapor 3 example

Here is actual code for Vapor 3.0:

let directory = DirectoryConfig.detect()
let workPath = directory.workDir

let name = UUID().uuidString + ".png"
let imageFolder = "Public/images"
let saveURL = URL(fileURLWithPath: workPath).appendingPathComponent(imageFolder, isDirectory: true).appendingPathComponent(name, isDirectory: false)

return req.content.get(File.self, at: "image").map { file in
    do {
        try file.data.write(to: saveURL)
        return .ok
    } catch {
        throw Abort(.internalServerError, reason: "Unable to write multipart form data to file. Underlying error \(error)")
    }
}

@97mik how we can report progress back to the client?

There's no progress here to be reported. The client should track and display its own upload progress while sending the body.

I'm having trouble with this. Appreciate any help. Thanks in advance. Here is my function in my UserController:

    func uploadUser(_ req: Request) throws -> Future<HTTPStatus> {
        print("uploadUserImage")
        let directory = DirectoryConfig.detect()
        let workPath = directory.workDir

        let name = UUID().uuidString + ".jpg"
        let imageFolder = "profile/images"
        let saveURL = URL(fileURLWithPath: workPath).appendingPathComponent(imageFolder, isDirectory: true).appendingPathComponent(name, isDirectory: false)

        return req.content.get(FileContent.self, at: "file").map { payload in
            do {
                try payload.file.data.write(to: saveURL)
                print("payload: \(payload)")
               return .ok
            } catch {
                print("error: \(error)")
                throw Abort(.internalServerError, reason: "Unable to write multipart form data to file. Underlying error \(error)")
            }
        }
    }

struct FileContent: Content {
    var file: File
}

And here's my route:

 router.post("json", User.parameter, use: userController.uploadUser)

And here is how I call it in Swift 4, iOS:

    func saveImage(image: UIImage?) {
        guard let imageToSave = image else {
            return
        }

       let urlString = "http://192.168.1.5:8080/json/uploadUser"

        let imgData = imageToSave.jpegData(compressionQuality: 0.7)!

        let imageName = "testImage.jpg"

        Alamofire.upload(multipartFormData: { multipartFormData in
            multipartFormData.append(imgData, withName: "file",fileName: imageName, mimeType: "image/jpeg") }, to:urlString) { (result) in
                switch result {

                case .success(let upload, _, _):

                    upload.uploadProgress(closure: { (progress) in
                        print("Upload Progress: \(progress.fractionCompleted)")
                    })

                    upload.responseJSON { response in
                        print("response.result :\(String(describing: response.result.value))")
                    }

                case .failure(let encodingError):
                    print(encodingError)
                }
        }
    }

I get upload progress and then an error : MultipartError.arrayOffset: Nested form-data is not supported. (FormDataDecoder.swift:51)

@thedreadbot there seems to be a subtle mistake.

Replace this line:

return req.content.get(FileContent.self, at: "file").map { payload in

with:

return req.content.decode(FileContent.self).map { payload in

Thank you! Very helpful. I had to put in a try though. Can you tell me how I can return the URL?
```swift
func uploadUser(_ req: Request) throws -> Future {
print("uploadUserImage")
let directory = DirectoryConfig.detect()
let workPath = directory.workDir

    let name = UUID().uuidString + ".jpg"
    let imageFolder = "profile/images"
    let saveURL = URL(fileURLWithPath: workPath).appendingPathComponent(imageFolder, isDirectory: true).appendingPathComponent(name, isDirectory: false)

    return try req.content.decode(FileContent.self).map { payload in
        do {
            try payload.file.data.write(to: saveURL)
            print("payload: \(payload)")
           return .ok
        } catch {
            print("error: \(error)")
            throw Abort(.internalServerError, reason: "Unable to write multipart form data to file. Underlying error \(error)")
        }
    }
}```
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jsl303 picture jsl303  ·  4Comments

kdawgwilk picture kdawgwilk  ·  4Comments

litan1106 picture litan1106  ·  4Comments

olivier38070 picture olivier38070  ·  3Comments

betzerra picture betzerra  ·  3Comments