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
}
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)")
}
}
}```
Most helpful comment
To save files that are uploaded through multipart requests, you simply have to use
FileManager, just like you would do oniOS.Here is the code I use for saving files :