티스토리 뷰

1. 이미지를 baseString으로 바꿔서, String으로 전송하기

2. 이미지를 Data로 전환해서 Multipart로 전송하기

 

주로 서버 개발자들이 2번으로 많이 해서 주는듯 하다. 

 

 

postman도 다음과 같이 Body가 form-data로 되어있는걸 확인할 수 있다. 

사실 나는 단순히 header에 content type을 multipart로 설정하고, JSON에 담아서 보내면 될 줄 알았는데 그건 아닌 것 같다. 

 

 

 

JSON body 업로드

기존에 JSON같은 경우에는 JSONSerialization.data(withJSONObject:)를 사용했는데 form-data는 그것보다는 조금 복잡하다. 

 

func uploadImage(paramName: String, fileName: String, image: UIImage) {
    let url = URL(string: "http://api-host-name/v1/api/uploadfile/single")

    // 바운더리를 구분하기 위한 임의의 문자열. 각 필드는 `--바운더리`의 라인으로 구분된다. 
    let boundary = UUID().uuidString
    let session = URLSession.shared

    // URLRequest 생성하기
    var urlRequest = URLRequest(url: url!)
    urlRequest.httpMethod = "POST"

	// Boundary랑 Content-type 지정해주기.
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var data = Data()

    // --(boundary)로 시작. 
    data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
    // 헤더 정의 - 문자열로 작성 후 UTF8로 인코딩해서 Data타입으로 변환해야 함
    data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
	// 헤더 정의 2 - 문자열로 작성 후 UTF8로 인코딩해서 Data타입으로 변환해야 함, 구분은 \r\n으로 통일. 
	data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
    // 내용 붙이기
    data.append(image.pngData()!)

	// 모든 내용 끝나는 곳에 --(boundary)--로 표시해준다. 
    data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

    // Send a POST request to the URL, with the data we created earlier
    session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
        if error == nil {
            let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
            if let json = jsonData as? [String: Any] {
                print(json)
            }
        }
    }).resume()
}

왜 줄이 들어가는지는 soooprmx.com/archives/9529 여기 참고.

 

--XXXXX
Content-Disposition: form-data; name="file"; filename="image.jpg"
Content-Type: image/jpeg

.....{파일의 바이너리 데이터}
--XXXXX--

 

일단 대용량 업로드는 URLSession.datatask가 아니라 URLSession.uploadTask를 사용한다고 한다. 

만약, 하나의 필드만 입력하는게 아니라 나의 postman처럼 여러개의 필드를 같이 업로드해야한다면 어떻게 해야할까??

 

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

var semaphore = DispatchSemaphore (value: 0)

let parameters = [
  [
    "key": "key",
    "value": "value",
    "type": "text"
  ]] as [[String : Any]]

let boundary = "Boundary-\(UUID().uuidString)"
var body = ""
var error: Error? = nil
for param in parameters {
  if param["disabled"] == nil {
    let paramName = param["key"]!
    body += "--\(boundary)\r\n"
    body += "Content-Disposition:form-data; name=\"\(paramName)\""
    if param["contentType"] != nil {
      body += "\r\nContent-Type: \(param["contentType"] as! String)"
    }
    let paramType = param["type"] as! String
    if paramType == "text" {
      let paramValue = param["value"] as! String
      body += "\r\n\r\n\(paramValue)\r\n"
    } else {
      let paramSrc = param["src"] as! String
      let fileData = try NSData(contentsOfFile:paramSrc, options:[]) as Data
      let fileContent = String(data: fileData, encoding: .utf8)!
      body += "; filename=\"\(paramSrc)\"\r\n"
        + "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n"
    }
  }
}
body += "--\(boundary)--\r\n";
let postData = body.data(using: .utf8)

var request = URLRequest(url: URL(string: "https://apitest....")!,timeoutInterval: Double.infinity)
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

request.httpMethod = "POST"
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) { data, response, error in 
  guard let data = data else {
    print(String(describing: error))
    semaphore.signal()
    return
  }
  print(String(data: data, encoding: .utf8)!)
  semaphore.signal()
}

task.resume()
semaphore.wait()

 

 

+) Combine을 사용하면서 나타난 문제점: 왜 session.uploadTaskPublisher은 없을까?

 

 

 

 

 

 

 

 

 

 

** Alamofire로 업로드 하는 코드 **

/// 첨부파일 저장
  static func attatchFile(groupType: GroupType, pageInfo: String, fileName: URL, postingID: Int, callback: @escaping ((Bool)?) -> Void) {
    var router: Router?

    switch groupType {
    case .alumni:
      router = Router.attachFileAlumni(pageInfo: pageInfo, fileName: fileName, postingID: postingID)
    case .council:
      router = Router.attachFileCouncil(pageInfo: pageInfo, fileName: fileName, postingID: postingID)
    case .group:
      router = Router.attachFileGroup(pageInfo: pageInfo, fileName: fileName, postingID: postingID)
    case .dev:
      router = Router.attachFileDev(pageInfo: pageInfo, fileName: fileName, postingID: postingID)
    }

    AF.upload(multipartFormData: { multiPart in
      for (key, value) in ["page_info": pageInfo, "postingid": postingID] as [String: Any] {
        if let temp = value as? String {
          multiPart.append(temp.data(using: .utf8)!, withName: key)
        }
        if let temp = value as? Int {
          multiPart.append("\(temp)".data(using: .utf8)!, withName: key)
        }
      }
      multiPart.append(fileName, withName: "fileName", fileName: "\(fileName)", mimeType: "image/png")
    }, with: router!)
      .uploadProgress(queue: .main, closure: { progress in
        // Current upload progress of file
        print("Upload Progress: \(progress.fractionCompleted)")
        })
      .debugLog()
      .responseJSON(completionHandler: { response in
        debugPrint(response)
        guard let data = response.data else { return }
        print(String(decoding: data, as: UTF8.self))
        print(String(decoding: (response.request?.httpBody)!, as: UTF8.self))

        switch response.result {
        case .failure:
          callback(false)
        case .success:
          callback(true)
        }
        })
  }

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함