Multipart Request with URLSession and async/await in Swift

AsyncLearn
4 min readSep 19, 2023

--

A Multipart request is used when you need to send a file along with a JSON object. The file can be audio, an image, a video, and so on.

There are many tutorials that demonstrate how to make a multipart request using Alamofire, which is a third-party library for managing the network layer of an application. However, there is limited information available on using URLSession.

In this article, we will perform a multipart request using URLSession without focusing on creating a network layer from scratch.

Step 1: Create an HTTPClient Protocol

First, create a protocol named HTTPClient:

protocol HTTPClient {
func sendMultipartRequest<T: Decodable>(responseModel: T.Type, data: Data?) async -> Result<T, Error>
}

Step 2: Create an Extension for the HTTPClient

Now, create a private extension for HTTPClient with a createBody function:

private extension HTTPClient {
func createBody(boundary: String, data: Data, mimeType: String, filename: String) -> Data {
let body = NSMutableData()
let boundaryPrefix = "--\(boundary)\r\n"

body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
body.append("--".appending(boundary.appending("--")))

return body as Data
}
}

The parameters for this function are:

  • boundary: A parameter used by the server to know where the sent value begins and ends.
  • data: What you send to the server, which can be audio, an image, video, etc.
  • mimeType: A standardised way to indicate the format of the sent file. You can see various types on this page: MimeTypes
  • filename: The name of the file being sent.

This function returns the body in Data format.

You can see that the boundary is added at the beginning and end of the body to indicate where it starts and ends.

Then, the Content-Disposition, Content-Type, and the data to be sent are added to the body:

body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)

Step 3: Create a New Extension for the HTTPClient Protocol with Implementation

func sendMultipartRequest<T: Decodable>(responseModel: T.Type, data: Data?) async -> Result<T, Error> {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "YOUR HOST"
urlComponents.path = "YOUR PATH"
urlComponents.port = YOUR PORT

guard let url = urlComponents.url, let data = data else {
return .failure(.invalidURL)
}

let filename = "name_\(Date().toString()).wav"
let boundary = "Boundary-\(UUID().uuidString)"

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = createBody(
boundary: boundary,
data: data,
mimeType: "audio/wav",
filename: filename
)

do {
let (data, response) = try await URLSession.shared.data(for: request, delegate: nil)
guard let response = response as? HTTPURLResponse else {
return .failure(.noResponse)
}

guard let decodedResponse = try? JSONDecoder().decode(responseModel, from: data) else {
return .failure(.decode)
}

return .success(decodedResponse)
} catch {
return .failure(.unknown)
}
}

Now, let’s break down what this function does step by step:

  1. Create the URLComponents:
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "YOUR HOST"
urlComponents.path = "YOUR PATH"
urlComponents.port = YOUR PORT

guard let url = urlComponents.url, let data = data else {
return .failure(.invalidURL)
}

Here, you specify the information about the endpoint where you want to send the file. Then, it checks if you have a valid URL before proceeding. The .failure(.invalidURL) is a custom error type created for the example; you can use any error type you prefer.

2. Create the URLRequest:

let filename = "name_\(Date().toString()).wav"
let boundary = "Boundary-\(UUID().uuidString)"

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = createBody(
boundary: boundary,
data: data,
mimeType: "audio/wav",
filename: filename
)

First, you create a URLRequest with the URL you generated using URLComponents. Then, you set the httpMethod to POST and define the Content-Type in the HTTP header as multipart/form-data. Finally, you assign the httpBody using the createBody function you previously created.

3. Get the server’s response:

Use the new Swift function to get both the Data and the Response:

let (data, response) = try await URLSession.shared.data(for: request, delegate: nil)

With the data, you can decode and retrieve the expected model:

guard let decodedResponse = try? JSONDecoder().decode(responseModel, from: data) else {
return .failure(.decode)
}

return .success(decodedResponse)

With the response, you can check its status. First, make sure the response is not nil:

guard let response = response as? HTTPURLResponse else {
return .failure(.noResponse)
}

You can also use a switch statement on the statusCode of the response to return an error type, depending on what the server sends:

switch response.statusCode {
case 200...299:
guard let decodedResponse = try? JSONDecoder().decode(responseModel, from: data) else {
return .failure(.decode)
}
return .success(decodedResponse)
case 401...403:
return .failure(.unauthorized)
case 400:
return .failure(.badRequest)
case 500:
return .failure(.internalServer)
default:
return .failure(.unexpectedStatusCode)
}

To wrap up, use the function you created:

  1. Create a structure called DataProvider that implements our HTTPClient protocol:
struct DataProvider: HTTPClient {
func postAudio(data: Data) async -> Result<Model, Error> {
await sendMultipartRequest(responseModel: Model.self, data: data)
}
}

2. Call our DataProvider:

let result = await DataProvider().postAudio(data: data)

As you can see, by using the DataProvider based on the HTTPClient protocol created earlier, you can send the desired data.

If you want to read the Spanish version of this article, you can find it here: https://asynclearn.com/blog/multipart-request-con-urlsession-asyncawait/

--

--

AsyncLearn

Stay up-to-date in the world of mobile applications with our specialised blog.