Swift - URLSession Request: GET, POST, multipart-form/data

2 분 소요


지금 프로젝트에서는 Moya(Alamofire 추상화한 라이브러리)를 사용하고 있는데, 외주 작업하는 데서는 URLSession으로 직접 하더라

multipart-form/data도 별로 다뤄본 적 없고 그래서 이번 기회에 좀 곤란했던 부분들 정리해 봄

  1. 이미지 보내기?? 이렇게
  2. 이미지랑 이미지가 아닌 파라미터들 같이 보내기?? 이렇게
  3. 이미지가 아닌 파라미터가 Array일 때?? 이렇게


기본적인 리퀘스트

그냥 간단하게 추가해 봄니다

GET request

static func getGETRequest(url: String, data: Dictionary<String, Any>) -> URLRequest? {
    var urlComponents = URLComponents(string: url)
    data.forEach { (key: String, value: Any) in
        urlComponents?.queryItems?.append(URLQueryItem(name: key, value: "\(value)"))
    }
    guard let url = urlComponents?.url else { return nil }
    
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    return request
}

GET은 쿼리로 보낸다. 제일 간단


POST request

static func getPOSTRequest(url: String, data: Dictionary<String, Any>) -> URLRequest? {
    guard let url = URL(string: url) else { return nil }
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    
    let jsonData = try! JSONSerialization.data(withJSONObject: data, options: [])
    request.httpBody = jsonData
    return request
}

POST는 보통 바디에 json 형식으로 담아 보낸다


이외에도 바디를 json 형식으로 보낼 거면 PATCH면 걍 httpMethod를 PATCH로 바꾸고 하면 됨


multipart-form/data

파일 데이터 등을 보낼 때 바디를 구성하는 방법이다.
얘는 데이터 전송 방식이 다른 거기 때문에 당연히 POST, PATCH 등 다양하게 결합 가능

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

일단 리퀘스트 생성할 때 httpMethod 알아서 정하고
헤더의 Content-Typemultipart/form-data; boundary=\(boundary)로 지정해 줘야 함

static func getMultipartFormData(boundary: String, params: Dictionary<String, Any>, images: [UIImage]) -> Data {
    var data = Data()
    
    let boundaryPrefix = "--\(boundary)\r\n"
    let boundarySuffix = "--\(boundary)--\r\n"
    
    data.append(convertParams(params, boundaryPrefix))
    data.append(convertImagesData(images, boundaryPrefix))
    data.append(boundarySuffix.data(using: .utf8)!)
    
    return data
}

그 다음 데이터(바디)를 얻어줍니다
저거 주의해야 함… 바운더리를 각 파라미터 마다 앞에다 붙이고 마지막에도 붙여주는데 마지막에는 "--" 한 번 더 붙음ㅋㅋ

아무튼 그냥 있는 애들 다 변환해서 붙여주면 된다

그냥 파라미터들 보내기

static func convertFormField(key: String, value: String, _ boundaryPrefix: String) -> Data {
    var data = Data()

    data.append(boundaryPrefix.data(using: .utf8)!)
    data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
    data.append("\(value)\r\n".data(using: .utf8)!)
    
    return data
}
static func convertParams(_ params: Dictionary<String, Any>, _ boundaryPrefix: String) -> Data {
    var data = Data()
    params.forEach { (key, value) in
        data.append(convertFormField(key: key, value: "\(value)", boundaryPrefix))
    }
    return data
}

일단 파일이 아닌 파라미터들의 경우 이런 식으로 하나하나씩 붙여서 보내면 된다.

파일 데이터 보내기

static func convertImagesData(_ images: [UIImage], _ boundaryPrefix: String) -> Data {
    var data = Data()
    for image in images {
        guard let imageData = image.jpegData(compressionQuality: 0.5) else { return data }
        
        let fileName = "\(UUID().uuidString).jpg"
        let fieldName = "image"
        let mimeType = "image/jpeg"
        data.append(boundaryPrefix.data(using: .utf8)!)
        data.append("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
        data.append(imageData)
        data.append("\r\n".data(using: .utf8)!)
    }
    return data
}
  • fileName: 보낼 파일 이름
  • fieldName: 파라미터 이름
  • mimeType: 파일 타입 위 예시는 이미지이고 파일 보낼 때는 mimeType을 바꿔 주면 됨


근데!! 이렇게만 하면 잘 될까요

파라미터가 배열이면

대충 글 쓰기 API가 있어서, title, tags, images 등을 보낼 필요가 있었다.
뭐 대충 보내니까 잘 됨… 근데 해당 태그 게시글들 불러오니까 방금 쓴 애가 없네?? 아니, 글 쓰고 나서 리스폰스도 정상적으로 됐다고 오는데 왜 리스트 불러오면 얘가 없지?? 했더니

tags 파라미터의 값이 "["Tag1", "Tag2"]" 같은 형식으로 가고 있었음ㅋㅋ
태그가 각각 따로 "Tag1", "Tag2"로 저장이 안 되고 말그대로 "["Tag1", "Tag2"]"라는 태그로 저장이 되었다. 어레이가 어레이가 아니엇네용ㅎ

static func convertParams(_ params: Dictionary<String, Any>, _ boundaryPrefix: String) -> Data {
    var data = Data()
    params.forEach { (key, value) in
        // Array check
        if let arr = value as? Array<Any> {
            arr.forEach { elem in
                data.append(convertFormField(key: key, value: "\(elem)", boundaryPrefix))
            }
        } else {
            data.append(convertFormField(key: key, value: "\(value)", boundaryPrefix))
        }
    }
    return data
}

여기서 Array인 경우 각각 중복되게 "tags": "Tag1", "tags": "Tag2"과 같이 다시 넣어주니까 정상 동작 함니다… 생각해보니까 파일 여러 개 보낼 때 저런 식으로 똑같이 필드네임 중복해서 각각 다 넣어 주는데 왜 몰랐을까


굿


태그: ,

카테고리:

업데이트:

댓글남기기