我想使用某些URL端点将图像文件上传到后端服务器。我可以使用Alamofire的上载请求作为multipartFormData轻松地做到这一点。但是,我想摆脱Alamofire,以最大程度地减少对第三方框架的依赖。
这是有效的Alamofire代码:
func uploadRequestAlamofire(parameters: [String: Any], imageData: Data?, completion: @escaping(CustomError?) -> Void ) {
let url = imageUploadEndpoint!
let headers: HTTPHeaders = ["X-User-Agent": "ios",
"Accept-Language": "en",
"Accept": "application/json",
"Content-type": "multipart/form-data",
"ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""]
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as String)
}
if let data = imageData {
multipartFormData.append(data, withName: "file", fileName: "image.png", mimeType: "image/jpg")
}
}, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers) { (result) in
switch result {
case .success(let upload, _, _):
upload.responseJSON { response in
completion(CustomError(errorCode: response.response!.statusCode))
print("Succesfully uploaded")
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}
}
这是URLSession上传任务,该任务不起作用:
func requestNativeImageUpload(imageData: Data, orderExtId: String) {
var request = URLRequest(url: imageUploadEndpoint!)
request.httpMethod = "POST"
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"X-User-Agent": "ios",
"Accept-Language": "en",
"Accept": "application/json",
"Content-type": "multipart/form-data",
"ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
]
let body = OrderUpload(order_ext_id: orderExtId, file: imageData)
do {
request.httpBody = try encoder.encode(body)
} catch let error {
print(error.localizedDescription)
}
let session = URLSession.shared
session.uploadTask(with: request, from: imageData) { data, response, error in
guard let response = response as? HTTPURLResponse else { return }
print(response)
if error != nil {
print(error!.localizedDescription)
}
}.resume()
}
这是我为Alamofire和URLSession调用方法的方式:
uploadRequestAlamofire(parameters: ["order_ext_id": order_ext_id, "file": "image.jpg"], imageData: uploadImage) { [weak self] response in }
requestNativeImageUpload(imageData: uploadImage!, orderExtId: order_ext_id)
后端服务器希望在请求正文中收到以下内容:
let order_ext_id: String
let description: String
let file: string($binary)
这是为请求的httpBody编码的Codable结构。
struct OrderUpload: Codable {
let order_ext_id: String
let description: String
let file: String
}
尽管在此演示中,我的方法可能不完全适用,并且我不处理响应状态代码,但Alamofire方法效果很好。
为什么URLSession不起作用?
最佳答案
终于我找到了解决方案。
来源是:URLSession: Multipart Form-Data Requests | Swift 3, Xcode 8。
在我的特定情况下,我需要提供orderExtId作为后端服务器接受我的图像的参数。您的情况可能会有所不同,具体取决于后端的要求。
func requestNativeImageUpload(image: UIImage, orderExtId: String) {
guard let url = imageUploadEndpoint else { return }
let boundary = generateBoundary()
var request = URLRequest(url: url)
let parameters = ["order_ext_id": orderExtId]
guard let mediaImage = Media(withImage: image, forKey: "file") else { return }
request.httpMethod = "POST"
request.allHTTPHeaderFields = [
"X-User-Agent": "ios",
"Accept-Language": "en",
"Accept": "application/json",
"Content-Type": "multipart/form-data; boundary=\(boundary)",
"ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
]
let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
request.httpBody = dataBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
func generateBoundary() -> String {
return "Boundary-\(NSUUID().uuidString)"
}
func createDataBody(withParameters params: [String: String]?, media: [Media]?, boundary: String) -> Data {
let lineBreak = "\r\n"
var body = Data()
if let parameters = params {
for (key, value) in parameters {
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append("\(value + lineBreak)")
}
}
if let media = media {
for photo in media {
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.fileName)\"\(lineBreak)")
body.append("Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
body.append(photo.data)
body.append(lineBreak)
}
}
body.append("--\(boundary)--\(lineBreak)")
return body
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
struct Media {
let key: String
let fileName: String
let data: Data
let mimeType: String
init?(withImage image: UIImage, forKey key: String) {
self.key = key
self.mimeType = "image/jpg"
self.fileName = "\(arc4random()).jpeg"
guard let data = image.jpegData(compressionQuality: 0.5) else { return nil }
self.data = data
}
}
关于ios - 如何在Swift中使用Codable和URLSession.shared.uploadTask(multipart/form-data)上传图像文件?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58235857/