How to handle multipart request with Vapor 3

2020-06-21 07:40发布

问题:

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.

回答1:

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
    })
}


标签: swift vapor