4

I'm a vapor beginner and I chose to start with Vapor 3-rc because it seems to break change from Vaport 2. Unfortunately, there isn't a complete documentation for now.

I'm currently trying to upload a simple txt file from Postman to my Vapor 3 local server.

Here's my route

let uploadController = FileUploadController()
router.post("uploadtxt", use: uploadController.uploadTXT)

and my controller

final class FileUploadController {
    func uploadTXT(_ req: Request) throws -> Future<String> {
        return try req.content.decode(MultipartForm.self).map(to: String.self, { form in
            let file = try form.getFile(named: "txtfile")
            return file.filename ?? "no-file"
        })
    }
}

First, by executing the Postman request, the server returns:

{"error":true,"reason":"There is no configured decoder for multipart\/form-data; boundary=...}

By investigating the source code and the limited documentation on this, it seems that I should declare a decoder to support multipart incoming requests.

So I did:

var contentConfig = ContentConfig.default()
let decoder = FormURLDecoder()
contentConfig.use(decoder: decoder, for: .multipart)
services.register(contentConfig)

I used FormURLDecoder because it seemed to be the closest class for my needs IMO, implementing BodyDecoder

Now it infite-loops into func decode<T>(_ type: T.Type) throws -> T where T: Decodable of FormURLSingleValueDecoder, and I'm stuck here with very few web resource.

Martin
  • 11,881
  • 6
  • 64
  • 110

1 Answers1

5

I ended on the Vapor slack, which is a good place to find some info & a bit of help.

The solution is quite simple. Instead of using req.content.decode(MultipartForm.self), prefer use MultipartForm.decode(from: req) (...deleted code sample)

EDIT:

AS @axello said, MultipartForm does not exist anymore. I'm now using req.content.decode(...) method to parse the multipart data. The idea is to create an object that reflects your HTML form inputs. And Codable will magically map the data into the object for you.

For example, with this form:

<form method="POST" action="upload" enctype="multipart/form-data" class="inputForm">
     <input type="name" name="filename">
     <input type="file" name="filedata">
     <input type="submit" name="GO" value="Send" class="send">
</form>

I created this small struct

fileprivate struct MyFile: Content {
    var filename: String
    var filedata: Data
}

And, in my controller:

func uploadTXT(_ req: Request) throws -> Future<String> {
    return try req.content.decode(MyFile.self).map(to: String.self, { myFile in
        let filename = myFile.filename // this is the first input
        // ... and the second one:
        guard let fileContent = String(data: myFile.filedata, encoding: .utf8) else {
            throw Abort(.badRequest, reason: "Unreadable CSV file")
        }
        print(fileContent)
        return filename
    })
}
Martin
  • 11,881
  • 6
  • 64
  • 110
  • There is so much going on in these little controllers and I find them very hard to follow exactly what each snippet is accomplishing. I just asked a question but then read your post. I think maybe I need to use multipartform also (but I'm not sure how)? https://stackoverflow.com/questions/49594004/how-to-pass-data-from-using-post-form-leaf-template – Narwhal Apr 01 '18 at 01:43
  • I just read your post, you don't need multipart. Multipart is for file uploading, you need either `form-data` or `x-www-urlencoded` – Martin Apr 04 '18 at 08:11
  • 1
    Fixed in Vapor 3.0.0 rc 2.4. Docs updated: https://docs.vapor.codes/3.0/multipart/overview/ – nathan Apr 13 '18 at 05:47
  • 1
    Not really fixed, as MultipartForm does not exist anymore in Multipart v 3.0.0 – axello Apr 15 '18 at 20:07
  • @axello at last, i edited my post to reflect the API changes. – Martin Sep 24 '19 at 21:37
  • 1
    0 Thanks for updating your answer @Martin! The Vapor team really liked to 'move fast and break things'. Which is okay, if it was documented at the same time, but Tanner used some magic to decode the forms and I could not wrap my head around it. All the other blogs, magic books etc. were obsolete as well. Most of them have been updated of course. Still nice that this snippet of internet wisdom is now correct as well! – axello Sep 26 '19 at 07:07