So you are making your first face beautifier© app and it’s about time to upload some images to a server. The backend person asks you to do it via a type of HTTP POST request known as multipart/form-data
. Soon you come to realise that URLSession
does not provide you with an out of the box URLRequest
or DataTask
for this specific task, despite the fact that this is a very standard way of uploading data.
And that’s when you reach for Alamofire.
Or you can stick with URLSession
and make your own MultipartFormDataRequest
It works like this:
func uploadImage(imageData: Data) {
let request = MultipartFormDataRequest(url: URL(string: "https://server.com/uploadPicture")!)
request.addDataField(named: "profilePicture", data: imageData, mimeType: "img/jpeg")
URLSession.shared.dataTask(with: request, completionHandler: {
...
}).resume()
}
MultipartFormDataRequest
is very similar to a URLRequest
in that it is initialised by a URL. The URL endpoint that you will be sending data to.
Use addTextField
to send text and addDataField
to send anything that can be converted to Data
(images, audio, Grand Theft Auto 2 savefiles, you name it)
struct MultipartFormDataRequest {
private let boundary: String = UUID().uuidString
private var httpBody = NSMutableData()
let url: URL
init(url: URL) {
self.url = url
}
func addTextField(named name: String, value: String) {
httpBody.append(textFormField(named: name, value: value))
}
private func textFormField(named name: String, value: String) -> String {
var fieldString = "--\(boundary)\r\n"
fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
fieldString += "Content-Type: text/plain; charset=ISO-8859-1\r\n"
fieldString += "Content-Transfer-Encoding: 8bit\r\n"
fieldString += "\r\n"
fieldString += "\(value)\r\n"
return fieldString
}
func addDataField(named name: String, data: Data, mimeType: String) {
httpBody.append(dataFormField(named: name, data: data, mimeType: mimeType))
}
private func dataFormField(named name: String,
data: Data,
mimeType: String) -> Data {
let fieldData = NSMutableData()
fieldData.append("--\(boundary)\r\n")
fieldData.append("Content-Disposition: form-data; name=\"\(name)\"\r\n")
fieldData.append("Content-Type: \(mimeType)\r\n")
fieldData.append("\r\n")
fieldData.append(data)
fieldData.append("\r\n")
return fieldData as Data
}
}
extension NSMutableData {
func append(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
You also need a function to convert a MultipartFormDataRequest
to a URLRequest
in order to be able to create URLSessionDataTasks
func asURLRequest() -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
httpBody.appendString("--\(boundary)--")
request.httpBody = httpBody as Data
return request
}
Bonus round:
A handy URLSession
extension. You can also make this an upload task, or use background sessions etc.
extension URLSession {
func dataTask(with request: MultipartFormDataRequest,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask {
return dataTask(with: request.asURLRequest(), completionHandler: completionHandler)
}
}
And this is it. You can now upload your image to the server. A very good example of how simple (or scary) HTTP can be.
I didn’t go into much detail about the HTTP specifics because:
- This is a sort of tl;dr blog post where I’m sharing some (I hope) useful code with you
- Donny Wals has done an excellent job at explaining this in his blog post
- You can read about it here
Feel free to contact me for tips, feedback, opinions.
Thank you for reading!