Skip to content

Instantly share code, notes, and snippets.

@arturdev
Last active February 21, 2023 18:59
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save arturdev/defee12235d538118f803f54c33b99e2 to your computer and use it in GitHub Desktop.
Save arturdev/defee12235d538118f803f54c33b99e2 to your computer and use it in GitHub Desktop.
URLRequest with multipart support
//
// URLRequest+Multipart.swift
//
// Created by Artur Mkrtchyan on 1/16/19.
// Copyright © 2019 arturdev. All rights reserved.
//
import Foundation
import MobileCoreServices
extension URLRequest {
public enum HTTPMethod: String {
case connect
case delete
case get
case head
case options
case patch
case post
case put
case trace
}
class MultipartFormData {
var request: URLRequest
private lazy var boundary: String = {
return String(format: "%08X%08X", arc4random(), arc4random())
}()
init(request: URLRequest) {
self.request = request
}
func append(value: String, name: String) {
request.httpBody?.append("--\(boundary)\r\n".data())
request.httpBody?.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data())
request.httpBody?.append("\(value)\r\n".data())
}
func append(filePath: String, name: String) throws {
let url = URL(fileURLWithPath: filePath)
try append(fileUrl: url, name: name)
}
func append(fileUrl: URL, name: String) throws {
let fileName = fileUrl.lastPathComponent
let mimeType = contentType(for: fileUrl.pathExtension)
try append(fileUrl: fileUrl, name: name, fileName: fileName, mimeType: mimeType)
}
func append(fileUrl: URL, name: String, fileName: String, mimeType: String) throws {
let data = try Data(contentsOf: fileUrl)
append(file: data, name: name, fileName: fileName, mimeType: mimeType)
}
func append(file: Data, name: String, fileName: String, mimeType: String) {
request.httpBody?.append("--\(boundary)\r\n".data())
request.httpBody?.append("Content-Disposition: form-data; name=\"\(name)\";".data())
request.httpBody?.append("filename=\"\(fileName)\"\r\n".data())
request.httpBody?.append("Content-Type: \(mimeType)\r\n\r\n".data())
request.httpBody?.append(file)
request.httpBody?.append("\r\n".data())
}
fileprivate func finalize() {
request.httpBody?.append("--\(boundary)--\r\n".data())
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
}
}
init(multipartFormData constructingBlock: @escaping (_ formData: MultipartFormData) -> Void,
url: URL,
method: HTTPMethod = .post,
headers: [String: String] = [:])
{
self.init(url: url)
self.httpMethod = method.rawValue.uppercased()
self.httpBody = Data()
let formData = MultipartFormData(request: self)
constructingBlock(formData)
formData.finalize()
self = formData.request
for (k,v) in headers {
self.addValue(v, forHTTPHeaderField: k)
}
}
}
fileprivate func contentType(for pathExtension: String) -> String {
guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue() else {
return "application/octet-stream"
}
let contentTypeCString = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue()
guard let contentType = contentTypeCString as String? else {
return "application/octet-stream"
}
return contentType
}
fileprivate extension String {
func data() -> Data {
return self.data(using: .utf8) ?? Data()
}
}
@arturdev
Copy link
Author

Usage example

let request = URLRequest(multipartFormData: { (formData) in 
                                              //1. Example with URL to local file 
                                                  try? formData.append(fileUrl: URL(string: "path to file")!, name: "parameterKey")
                                              //2. Example with Data of a file
                                                  formData.append(file: avatarData, name: "avatar", fileName: "myImage.jpg", mimeType: "image/jpeg")
                                              //3. Example of key/value pair
                                                  formData.append(value: "John Doe", name: "fullName")
                                            }, 
                         url: <endpointUrl>, 
                         method: .post, 
                         headers: [:])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment