前言
最近在使用Vapor遇到很多的問(wèn)題掸屡,坑也填了不少解阅,下面就來(lái)說(shuō)說(shuō)由這個(gè)坑引發(fā)一系列的問(wèn)題。
需求
在應(yīng)用里闸氮,需要使用保存用戶(hù)的上傳頭像剪况,那么問(wèn)題來(lái)了,如果發(fā)布到heroku上蒲跨,空間是有限的译断,然而用戶(hù)量是不可估計(jì)的,所以在對(duì)比了國(guó)內(nèi)幾家的OSS后或悲,選擇了七牛對(duì)象存儲(chǔ)做為圖片的存儲(chǔ)空間孙咪,上傳的圖片的庫(kù)已經(jīng)找好,用的是Alamofire巡语。
問(wèn)題
在使用Alamofire時(shí)翎蹈,發(fā)現(xiàn)一個(gè)了問(wèn)題,我們都知道Alamofire這個(gè)庫(kù)使用得最多的iOS開(kāi)發(fā)男公,而用Alamofire做iOS的網(wǎng)絡(luò)請(qǐng)求荤堪,它的內(nèi)部返回的結(jié)果都是在主線(xiàn)程下執(zhí)行的,這樣做的確方便了iOS開(kāi)發(fā)的枢赔,但是在Vapor里主線(xiàn)程是會(huì)被攔截而不被觸發(fā)的澄阳,所以在使用Alamofire上傳圖片時(shí),結(jié)果是不會(huì)返回的踏拜。
思考
第一想到的是Alamofire是否有相關(guān)的API可以使用碎赢,但是遺憾的是,只有在返回結(jié)果后速梗,對(duì)結(jié)果進(jìn)行處理時(shí)才有肮塞,所以這個(gè)方案fail。第三方的實(shí)現(xiàn)不了姻锁,那只能自己實(shí)現(xiàn)這個(gè)功能了枕赵。
正文
在查看了七牛的文檔后屋摔,看到七牛上傳api是表單上傳烁设,先來(lái)看個(gè)示例。
Content-Type: multipart/form-data; boundary=分隔線(xiàn)
--分隔線(xiàn)
Content-Disposition: form-data; name="token"
<uploadToken>
--分隔線(xiàn)
Content-Disposition: form-data; name="key"
<key>
--分隔線(xiàn)
Content-Disposition: form-data; name="file"; filename="<fileName>"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
<fileBinaryData>
--分隔線(xiàn)--
上面的示例中钓试,需要傳入的參數(shù)有三個(gè),token,key,file。在七牛中副瀑,token是要自己生成的弓熏,這里就不多說(shuō)明了,想了解的話(huà)可以私信博主糠睡。上傳的boundary=分隔線(xiàn)
是給后臺(tái)解析時(shí)用的挽鞠,博主看到Alamofire里的是以這個(gè)String(format: "Alamofire.boundary.%08x%08x", arc4random(), arc4random())
為分隔線(xiàn)的,博主的分隔線(xiàn)只是把Alamofire給去掉。
最后生成像下面這樣:
Content-Type: multipart/form-data; boundary=boundary.73e735e3732b6c0e
知道怎么生成就開(kāi)始構(gòu)建了信认。
let url = URL(string:"http://up.qiniu.com")
var request = URLRequest.init(url: url!);
// 請(qǐng)求類(lèi)型
request.httpMethod = "POST";
// 超時(shí)時(shí)間
request.timeoutInterval = 30;
// 設(shè)置分隔線(xiàn)
let boundary = String(format: "boundary.%08x%08x", arc4random(), arc4random())
let contentType = String(format: "multipart/form-data;boundary=%@", boundary)
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
// 創(chuàng)建body
var body = Data();
// 請(qǐng)求參數(shù)
let dict = ["token":token,"key": key]
let keys = dict.keys;
for key in keys {
body.append(String(format:"--%@\r\n",boundary).data(using: .utf8)!)
body.append(String(format:"Content-Disposition:form-data;name=\"%@\"\r\n\r\n",key as String).data(using: .utf8)!)
body.append("\(dict[key]!)\r\n".data(using: .utf8)!)
}
// 數(shù)據(jù)之前要用 --分隔線(xiàn) 來(lái)隔開(kāi) 材义,否則后臺(tái)會(huì)解析失敗
body.append(String(format:"--%@\r\n",boundary).data(using: .utf8)!)
// 文件
let key = "1.jpg"
// 文件主體
let data = UIImagePNGRepresentation(UIImage.init(named: key)!);
let file = "file"
// 傳入最后一個(gè)參數(shù)
body.append(String(format:"Content-Disposition:form-data;name=\"%@\";filename=\"\(key)\"\r\n", file).data(using: .utf8)!)
// 文件類(lèi)型
body.append("Content-Type:image/jpeg\r\n\r\n".data(using: .utf8)!)
// 添加文件主體
body.append(data)
// 使用\r\n來(lái)表示這個(gè)這個(gè)值的結(jié)束符
body.append("\r\n".data(using: .utf8)!)
// --分隔線(xiàn)-- 為整個(gè)表單的結(jié)束符
body.append(String(format:"--%@--\r\n",boundary).data(using: .utf8)!)
// 上傳表單
URLSession.shared.uploadTask(with: request, from: body) { (data, resp, error) in
do{
let d = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(d)
}catch{
print(error)
}
}.resume()
上面的代碼在OS上是沒(méi)問(wèn)題的,但是在Linux上就會(huì)報(bào)錯(cuò)(更新于2017.9.6)
fatal error: shared is not yet implemented: file Foundation/NSURLSession/NSURLSession.swift
這是由于使用的URLSession.shared在Linux上還沒(méi)有被實(shí)現(xiàn)嫁赏,這里有說(shuō)道原因其掂。如果想了解還有那些在Linux上缺失的可以點(diǎn)這里。shared不能使用潦蝇,我們就換個(gè)方法款熬。
// 生成body的方法和上面的一樣
request.httpBody = body
// 使用URLSessionConfiguration.default來(lái)生成URLSession
let session = URLSession(
configuration:URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
let dataTask = session.dataTask(with: request, completionHandler: {[weak self] (data, response, error) -> Void in
let tuple = self?.c(data: data, response: response, err: error);
completion((tuple?.0)!,tuple?.1)
})
dataTask.resume()
以上就是一個(gè)很全面的一次表單多參數(shù)上傳的示例了,還有不明白的童鞋可以私信博主攘乒。