티스토리 뷰
1. 이미지를 baseString으로 바꿔서, String으로 전송하기
2. 이미지를 Data로 전환해서 Multipart로 전송하기
주로 서버 개발자들이 2번으로 많이 해서 주는듯 하다.
postman도 다음과 같이 Body가 form-data로 되어있는걸 확인할 수 있다.
사실 나는 단순히 header에 content type을 multipart로 설정하고, JSON에 담아서 보내면 될 줄 알았는데 그건 아닌 것 같다.
기존에 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)
}
})
}
'macOS, iOS' 카테고리의 다른 글
[iOS] device 정보 가지고 오기 (0) | 2021.02.25 |
---|---|
[SwiftUI] ButtonStyle (0) | 2021.02.23 |
[Swift] iOS13부터 get은 body갖는거 아예 허용 안됨 (0) | 2021.02.22 |
[SwiftUI] TabView (0) | 2021.02.20 |
[SwiftUI] 기존 iOS개발자를 위한 SwiftUI 입문 (0) | 2021.02.20 |