TIL
서버로 데이터를 전송하는 방법에는 HTTP POST 메서드를 이용하는 방법이 있다.
한 가지 타입의 데이터만 전송할 때는 상관이 없겠지만, 블로그 게시글 올리기
처럼 사진과 글 두개의 타입을 가진 데이터를 서버로 전송할 때는 이를 표현해 주기 위해 Content-Type 속성으로 multipart/form-data
라는 것을 사용하게 된다.
Content-Type은 리소스의 미디어 타입을 나타내기 위해 사용된다. MIME 타입의 전체 목록
application/x-www-form-urlencoded
콘텐츠 유형을 사용하는 간단한 형태의 예시이다.POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
say=Hi&to=Mom
multipart/form-data
Content-Type 을 사용하는 예시이다.POST /test.html HTTP/1.1
Host: example.org
Content-Type: multipart/form-data;boundary="boundary"
--boundary
Content-Disposition: form-data; name="field1"
value1
--boundary
Content-Disposition: form-data; name="field2"; filename="example.txt"
value2
--boundary--
Content-Type: multipart/form-data; boundary=3A42CBDB-01A2-4DDE-A9EE-425A344ABA1
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="family_name"
Wals
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="name"
Donny
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="file"; filename="somefilename.jpg"
Content-Type: image/png
-a long string of image data-
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13—
--boundary
을 시작으로 바운더리값이 경계가 되어 각각의 데이터가 들어간다. --boundary
으로 다른 데이터입력을 시작하거나 --boundary--
으로 끝낼 수 있다.이러한 형태의 multipart/form-data 를 Data
타입으로 완성 해주었다면, 만들어준 URLRequest
의 인스턴스.httpBody 에 값을 할당해주면 된다..!!
이러한 원리를 잘 숙지하여, 이제 실제로 데이터를 POST 해볼 차례이다 !
private func makeURL(base: String,
path: String,
parameters: [String: Any]?) -> URL? {
var urlComponent = URLComponents(string: base)!
urlComponent.path = path
return urlComponent.url
}
func createPostBody(with model: ProductModel, at boundary: String) -> Data? {
var data = Data()
guard let paramData = try? JSONEncoder().encode(model),
let images = images else { return nil }
let startBoundaryData = "\r\n--\(boundary)\r\n"
data.appendString(startBoundaryData)
data.appendString("Content-Disposition: form-data; name=\"params\"\r\n\r\n")
// "params" 는 필드네임 인데, 나의 요청의 필드네임은 params 와 images 였다 !
// 첫번째 데이터 이므로 필드네임에 params
data.append(paramData)
data.append(convertImages(images, using: boundary))
data.appendString("\r\n--\(boundary)--\r\n")
return data
}
private func convertImages(_ images: [UIImage?], using boundary: String) -> Data {
var data = Data()
for image in images {
guard let image = image else { break }
let imageData = image.convertToData()
data.append(convertFileData(fileName: "abc",
mimeType: "image/jpeg",
fileData: imageData,
using: boundary))
}
return data
}
private func convertFileData(fileName: String,
mimeType: String,
fileData: Data,
using boundary: String) -> Data {
var data = Data()
data.appendString("\r\n--\(boundary)\r\n")
data.appendString("Content-Disposition: form-data; name=\"images\"; filename=\"\(fileName).png\"\r\n")
// 필드네임에 images 가 들어간다
data.appendString("Content-Type: \(mimeType)\r\n\r\n")
data.append(fileData)
data.appendString("\r\n")
return data
}
\r\n
은 개행을 뜻한다.\"
은 쌍따옴표 안에서 "
를 표현하기 위해 쓴다.func makeURLRequest() -> URLRequest? {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let boundary = UUID().uuidString
let postBody = createPostBody(with: body, at: boundary)
urlRequest.httpBody = postBody
urlRequest.setValue(
URLCommand.identifier,
forHTTPHeaderField: "identifier"
)
urlRequest.setValue(
"multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type"
)
return urlRequest
}
let data = 만들어준 데이터
print(String(decoding: data, as: UTF8.self))
Test Suite 'APIConfigurationTest' started at 2022-11-17 22:58:01.945
Test Case '-[YagomMarketTests.APIConfigurationTest test_APIConfiguration의_credatePostBody메서드_테스트]' started.
--AAF06287-DF9C-4AF5-8661-5B835F624075
Content-Disposition: form-data; name="params"
{"stock":4,"currency":"USD","secret":"nvjb(비밀)kzv2","name":"유닛테스트","price":100,"description":"테스트용 모델"}
--AAF06287-DF9C-4AF5-8661-5B835F624075
Content-Disposition: form-data; name="images"; filename="abc.png"
Content-Type: image/jpeg
�4��8�ё����{��ނF($c�(,0y�p���21�P���@
B���(nM"���Ɯry�ăp�4�����FF��!��Aa���Bˑ�+E�Ԍ��R�1ր�؈�)��ri������./��Jp�M�/�8�N
9���
:�^��4*�x�(�p:P�A��@#�Z
�۽)�n��TzP@��|�?CJ�t})��}�
��⎁�U��OJ�x�����)�<R���@y�m������K�
)Q��d�8-�=�- �(*�H�08�(�@9�֚�c�
�;�����!�
h�1F=;P�H��E!Q��!#�����<�5�q�:qU��8��F:
@�"�O��֚㠠9��M���yȣh�JwSMo��S���28�M`�������4�����(����J/P1W���i�:z�9�:�?��zQ���c���*��}j&Q�8����8�(��b��c(�+(�N(٤��zSUWnqڀt��it�T~����������
2x��_0q��1@1��('����A@
H"�?)��)J��;��R`�
)U�H(qҐ�b���?A�iO_�on:�iv���C��Z6�G�8�c��>ԇ�F���I�;�"��=)P������ʔ�qMP�����P�����A����
�֚@�JR��;����4���+�x��@�)0��@�l}�ƞ:�Q� ��pPs�J���~��nJ@v1A��j�)� dq�AQ������(�1�P�Jd\�1J`qM�
�4t���
... 생략 ...
--AAF06287-DF9C-4AF5-8661-5B835F624075--
Test Case '-[YagomMarketTests.APIConfigurationTest test_APIConfiguration의_credatePostBody메서드_테스트]' passed (0.092 seconds).
Test Suite 'APIConfigurationTest' passed at 2022-11-17 22:58:02.039.
Executed 1 test, with 0 failures (0 unexpected) in 0.092 (0.094) seconds