Skip to content

Instantly share code, notes, and snippets.

@ryotapoi
Last active November 2, 2020 08:58
Show Gist options
  • Save ryotapoi/013d30937c90a7f98e1d53f220b9c36c to your computer and use it in GitHub Desktop.
Save ryotapoi/013d30937c90a7f98e1d53f220b9c36c to your computer and use it in GitHub Desktop.
multipart/form-dataを送るためのヘルパー for swift
import Foundation
public struct MIMEType: RawRepresentable, CustomStringConvertible {
public var rawValue: String
public var description: String { rawValue }
public init?(rawValue: String) {
self.rawValue = rawValue
}
public init(_ rawValue: String) {
self.rawValue = rawValue
}
}
extension MIMEType {
public static let png: MIMEType = .init("image/png")
public static let jpeg: MIMEType = .init("image/jpeg")
public static let mov: MIMEType = .init("video/quicktime")
public static let mp4: MIMEType = .init("video/mp4")
}
public struct MultipartFile {
var data: Data
var name: String
var fileName: String
var mimeType: MIMEType
var contentDipositionLine: String { "Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"" }
var contentTypeLine: String { "Content-Type: \(mimeType)" }
public init(_ data: Data, name: String, fileName: String, mimeType: MIMEType) {
self.data = data
self.name = name
self.fileName = fileName
self.mimeType = mimeType
}
}
public struct MultipartFormData {
let parameter: [String: String]
let files: [MultipartFile]
let boundary: String
private let separator = "\r\n".data(using: .utf8)!
public init(parameter: [String: String] = [:], files: [MultipartFile] = [], boundary: String = "\(UUID().uuidString)") {
self.parameter = parameter
self.files = files
self.boundary = boundary
}
public func urlRequest(url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = httpBody()
return request
}
public func urlRequestForUpload(url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
return request
}
public func httpBody() -> Data {
var data = Data()
for file in files {
data += "--\(boundary)".data(using: .utf8)! + separator
data += file.contentDipositionLine.data(using: .utf8)! + separator
data += file.contentTypeLine.data(using: .utf8)! + separator
data += separator
data += file.data + separator
}
for (key, value) in parameter {
data += "--\(boundary)".data(using: .utf8)! + separator
data += "Content-Disposition: form-data; name=\"\(key)\"".data(using: .utf8)! + separator
data += separator
data += value.data(using: .utf8)! + separator
}
data += "--\(boundary)--".data(using: .utf8)! + separator
return data
}
}
import XCTest
@testable import API
class MultipartFormData_ParameterTest: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let key = "keyString"
let value = "valueString"
func testParameter_empty() {
let multipartFormData = MultipartFormData(parameter: [:], files: [], boundary: boundary)
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n")
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)--"))
}
func testParameter_KeyValue() {
let key = "key"
let value = "value"
let multipartFormData = MultipartFormData(parameter: [key: value], files: [], boundary: boundary)
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(key)\""))
XCTAssertEqual(httpBodyLines[2], Substring(""))
XCTAssertEqual(httpBodyLines[3], Substring(value))
XCTAssertEqual(httpBodyLines[4], Substring("--\(boundary)--"))
}
func testParameter_TwoKeyValue() {
let key1 = "key1"
let value1 = "value1"
let key2 = "key2"
let value2 = "value2"
let multipartFormData = MultipartFormData(parameter: [key1: value1, key2: value2], files: [], boundary: boundary)
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
if httpBodyLines[1] == Substring("Content-Disposition: form-data; name=\"\(key1)\"") {
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(key1)\""))
XCTAssertEqual(httpBodyLines[2], Substring(""))
XCTAssertEqual(httpBodyLines[3], Substring(value1))
XCTAssertEqual(httpBodyLines[4], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[5], Substring("Content-Disposition: form-data; name=\"\(key2)\""))
XCTAssertEqual(httpBodyLines[6], Substring(""))
XCTAssertEqual(httpBodyLines[7], Substring(value2))
} else if httpBodyLines[1] == Substring("Content-Disposition: form-data; name=\"\(key2)\"") {
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(key2)\""))
XCTAssertEqual(httpBodyLines[2], Substring(""))
XCTAssertEqual(httpBodyLines[3], Substring(value2))
XCTAssertEqual(httpBodyLines[4], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[5], Substring("Content-Disposition: form-data; name=\"\(key1)\""))
XCTAssertEqual(httpBodyLines[6], Substring(""))
XCTAssertEqual(httpBodyLines[7], Substring(value1))
} else {
XCTFail()
}
XCTAssertEqual(httpBodyLines[8], Substring("--\(boundary)--"))
}
}
class MultipartFormData_PNGTest: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let formName = "formName"
let fileName = "test.png"
let pngData = Data([0x00, 0x01, 0x02])
lazy var file = MultipartFile(pngData, name: formName, fileName: fileName, mimeType: .png)
lazy var multipartFormData = MultipartFormData(parameter: [:], files: [file], boundary: boundary)
func testFile() {
let file = multipartFormData.files[0]
XCTAssertEqual(file.data, pngData)
XCTAssertEqual(file.fileName, fileName)
}
func testURLRequest() {
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
}
func testHTTPBody() {
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(fileName)\""))
XCTAssertEqual(httpBodyLines[2], Substring("Content-Type: image/png"))
XCTAssertEqual(httpBodyLines[3], Substring(""))
XCTAssertEqual(httpBodyLines[4], Substring("\u{00}\u{01}\u{02}"))
XCTAssertEqual(httpBodyLines[5], Substring("--\(boundary)--"))
}
}
class MultipartFormData_JPEGTest: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let formName = "formName"
let fileName = "test.jpg"
let jpegData = Data([0x10, 0x11, 0x12])
lazy var file = MultipartFile(jpegData, name: formName, fileName: fileName, mimeType: .jpeg)
lazy var multipartFormData = MultipartFormData(parameter: [:], files: [file], boundary: boundary)
func testFile() {
let file = multipartFormData.files[0]
XCTAssertEqual(file.data, jpegData)
XCTAssertEqual(file.fileName, fileName)
}
func testURLRequest() {
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
}
func testHTTPBody() {
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(fileName)\""))
XCTAssertEqual(httpBodyLines[2], Substring("Content-Type: image/jpeg"))
XCTAssertEqual(httpBodyLines[3], Substring(""))
XCTAssertEqual(httpBodyLines[4], Substring("\u{10}\u{11}\u{12}"))
XCTAssertEqual(httpBodyLines[5], Substring("--\(boundary)--"))
}
}
class MultipartFormData_JPEGWithParameterTest: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let key = "key"
let value = "value"
let formName = "formName"
let fileName = "test.jpg"
let jpegData = Data([0x10, 0x11, 0x12])
lazy var file = MultipartFile(jpegData, name: formName, fileName: fileName, mimeType: .jpeg)
lazy var multipartFormData = MultipartFormData(parameter: [key:value], files: [file], boundary: boundary)
func testParameter() {
XCTAssertEqual(multipartFormData.parameter, [key: value])
}
func testFile() {
let file = multipartFormData.files[0]
XCTAssertEqual(file.data, jpegData)
XCTAssertEqual(file.fileName, fileName)
}
func testURLRequest() {
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
}
func testHTTPBody() {
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(fileName)\""))
XCTAssertEqual(httpBodyLines[2], Substring("Content-Type: image/jpeg"))
XCTAssertEqual(httpBodyLines[3], Substring(""))
XCTAssertEqual(httpBodyLines[4], Substring("\u{10}\u{11}\u{12}"))
XCTAssertEqual(httpBodyLines[5], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[6], Substring("Content-Disposition: form-data; name=\"\(key)\""))
XCTAssertEqual(httpBodyLines[7], Substring(""))
XCTAssertEqual(httpBodyLines[8], Substring(value))
XCTAssertEqual(httpBodyLines[9], Substring("--\(boundary)--"))
}
}
class MultipartFormData_MOVTest: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let formName = "formName"
let fileName = "test.mov"
let movData = Data([0x20, 0x21, 0x22])
lazy var file = MultipartFile(movData, name: formName, fileName: fileName, mimeType: .mov)
lazy var multipartFormData = MultipartFormData(parameter: [:], files: [file], boundary: boundary)
func testFile() {
let file = multipartFormData.files[0]
XCTAssertEqual(file.data, movData)
XCTAssertEqual(file.fileName, fileName)
}
func testURLRequest() {
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
}
func testHTTPBody() {
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(fileName)\""))
XCTAssertEqual(httpBodyLines[2], Substring("Content-Type: video/quicktime"))
XCTAssertEqual(httpBodyLines[3], Substring(""))
XCTAssertEqual(httpBodyLines[4], Substring("\u{20}\u{21}\u{22}"))
XCTAssertEqual(httpBodyLines[5], Substring("--\(boundary)--"))
}
}
class MultipartFormData_Mp4Test: XCTestCase {
let boundary = "boundary_string"
let url = URL(string: "https://hoge.jp/form")!
let formName = "formName"
let fileName = "test.mp4"
let mp4Data = Data([0x30, 0x31, 0x32])
lazy var file = MultipartFile(mp4Data, name: formName, fileName: fileName, mimeType: .mp4)
lazy var multipartFormData = MultipartFormData(parameter: [:], files: [file], boundary: boundary)
func testFile() {
let file = multipartFormData.files[0]
XCTAssertEqual(file.data, mp4Data)
XCTAssertEqual(file.fileName, fileName)
}
func testURLRequest() {
let urlRequest = multipartFormData.urlRequest(url: url)
XCTAssertEqual(urlRequest.url, url)
XCTAssertEqual(urlRequest.httpMethod, "POST")
XCTAssertEqual(urlRequest.value(forHTTPHeaderField: "Content-Type"), "multipart/form-data; boundary=\(boundary)")
}
func testHTTPBody() {
let httpBody = multipartFormData.httpBody()
let httpBodyString = String(data: httpBody, encoding: .utf8)!
let httpBodyLines = httpBodyString.split(separator: "\r\n", maxSplits: Int.max, omittingEmptySubsequences: false)
XCTAssertEqual(httpBodyLines[0], Substring("--\(boundary)"))
XCTAssertEqual(httpBodyLines[1], Substring("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(fileName)\""))
XCTAssertEqual(httpBodyLines[2], Substring("Content-Type: video/mp4"))
XCTAssertEqual(httpBodyLines[3], Substring(""))
XCTAssertEqual(httpBodyLines[4], Substring("\u{30}\u{31}\u{32}"))
XCTAssertEqual(httpBodyLines[5], Substring("--\(boundary)--"))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment