URLSession으로 http프로토콜을 이용해 POST, PUT 통신을 할 때 기본적으로 Reqeust의 헤더에 Content-Type에 application/json을 붙여서 사용했는데, 이번에는 http프로토콜을 이용해 multipart/form으로 이미지를 전송해 보았다.
multipart/form 방식은 기존 application/json과 다르게 body를 json으로 보내는 것이 아닌 보내는 데이터를 여러 개로 쪼개서 보내는 방식이다.
Multipart/form Header
func settingHeader(identifier: String, request: URLRequest) -> URLRequest {
var copyReq = request
let contentType = "multipart/form-data; boundary=\(identifier)"
copyReq.addValue(contentType, forHTTPHeaderField: "Content-Type")
return copyReq
}
header의 boundary에 들어가는 값은 multipart의 나뉜 부분을 구분하기위해 사용되는 값이다. 이 boundary 값으로 데이터가 나뉜 부분과 어디서부터 시작이고 어디서부터 끝인지를 구분한다.
Multipart/form Body
만약 요청 body 파라미터가 다음과 같다고 해보자.
Key | value type | 설명 |
name | String | |
food | String | |
petImage | Data | 최대 2장 |
petImage는 jpeg 이미지를 보낸다고 가정했을때, request의 body는 다음과 같은 형식으로 이루어져 있다.
Content-Type: multipart/form-data; boundary=50178FC4-8E6A-4028-A194-E0B460FA4D4B
--50178FC4-8E6A-4028-A194-E0B460FA4D4B
Content-Disposition: form-data; name="name"
이름
--50178FC4-8E6A-4028-A194-E0B460FA4D4B
Content-Disposition: form-data; name="food"
음식
--50178FC4-8E6A-4028-A194-E0B460FA4D4B
Content-Disposition: form-data; name="petImage"; filename="profile50178FC4-8E6A-4028-A194-E0B460FA4D4B.jpg"
Content-Type: image/jpeg
<이미지 데이터>
--50178FC4-8E6A-4028-A194-E0B460FA4D4B
Content-Disposition: form-data; name="petImage"; filename="profile50178FC4-8E6A-4028-A194-E0B460FA4D4B.jpg"
Content-Type: image/jpeg
<이미지 데이터>
--50178FC4-8E6A-4028-A194-E0B460FA4D4B--
기존 json과 다르게 파라미터 값이 모두 구분되어져 있어야 하고, 띄어쓰기 역시 정확히 되어 있어야 한다. 만약 데이터가 들어가는 영역에 엔터가 한 줄 더 들어가거나 덜 들어가는 경우 데이터가 제대로 인식이 안되거나 포맷이 하나의 데이터로 인식되므로 주의할 것.
코드
Encoding할 값과 전달할 데이터의 값을 따로 구분.
Encoding 할 객체를 JSONSerialization을 이용하여 Dictionary 형태로 변환, 전달할 데이터 역시 Dictionary 형태로 값 전달
let identifier = UUID().uuidString
let body = try JSONEncoder().encode(bodyParameter)
let bodyParameterDict = try JSONSerialization.jsonObject(with: body) as? [String: Any]
let strBodyDict = bodyParameterDict.mapValues { String(describing: $0) }
request.httpBody = uploader.createPostBody(identifier: identifier, bodyParameterData: strBodyDict, datas: data)
Encoding 한 바디 파라미터 데이터를 Data 객체에 추가. 추가할 때마다 boundary값을 넣어주고, name에 파라미터의 이름값을 삽입.
func createPostBody(identifier: String, bodyParameterData: [String : String], datas: [String : [Data]]) -> Data {
let convertData = NSMutableData()
for parameter in bodyParameterData {
convertData.appendString("\r\n--\(identifier)\r\n")
convertData.appendString("Content-Disposition: form-data; name=\"\(parameter.key)\"\r\n\r\n")
convertData.appendString(parameter.value)
}
convertData.append(convertDatas(identifier: identifier, datas: datas))
convertData.appendString("--\(identifier)--\r\n")
return convertData as Data
}
extension NSMutableData {
func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
전달할 데이터는 Content-Type을 지정하여 어떤 데이터인지 표시후 데이터 삽입. 나의 경우 jpeg데이터를 전달하기에 jpeg로 따로 표기해 두었지만, 함수로 인자값을 받는 방식으로 구현해도 될 듯하다.
func convertDatas(identifier: String, datas: [String : [Data]]) -> Data {
let convertData = NSMutableData()
for data in datas {
for detailData in data.value {
convertData.appendString("\r\n--\(identifier)\r\n")
convertData.appendString("Content-Disposition: form-data; name=\"\(data.key)\"; filename=\"\(data.key)\(identifier).jpg\"\r\n")
convertData.appendString("Content-Type: image/jpeg\r\n\r\n")
convertData.append(detailData)
convertData.appendString("\r\n")
}
}
return convertData as Data
}
전체적인 코드: https://github.com/Kim-Junhwan/LSLP/tree/profile/LowServiceLevelProject/Network
참고 : https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/POST
'iOS > 앱 개발' 카테고리의 다른 글
Alamofire의 응답값을 처리하는 여러가지 방법 (1) | 2024.01.15 |
---|---|
API의 response status를 좀 더 구조적으로 핸들링 해보자 (0) | 2023.12.24 |
권한 요청, 설정 이동을 추상화(protocol)를 이용하여 재사용 가능하게 사용하기 (1) | 2023.11.21 |
MVVM Inout패턴 + 네트워크 에러 핸들링 (0) | 2023.11.10 |
렙케어 출시 프로젝트 회고 (0) | 2023.11.07 |