Last active
November 2, 2020 08:58
-
-
Save ryotapoi/013d30937c90a7f98e1d53f220b9c36c to your computer and use it in GitHub Desktop.
multipart/form-dataを送るためのヘルパー for swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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