我正在尝试使用URLSession将文件上传到s3存储桶。我知道要在后台上传文件,我需要使用这里提到的uploadTask(with:fromFile:)
方法。所以我正在执行以下步骤来上传文件。
- 创建后台URLSession
lazy var session: URLSession = {
let bundleIdentifier = Bundle.main.bundleIdentifier!
let config = URLSessionConfiguration.background(withIdentifier: bundleIdentifier + ".background")
config.sharedContainerIdentifier = bundleIdentifier
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
- 生成包含多部分数据的请求
数据模型
struct UploadRequest {
let destinationUrl: URL
let sourceURL: URL
let params: [String: String]
let fileName: String
let mimeType: String
}
private func requestAndPath(for
uploadParam: UploadRequest) -> (request: URLRequest,
filePath: URL)? {
// Create an empty file and append header, file content and footer to it
let uuid = UUID().uuidString
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let fileURL = directoryURL.appendingPathComponent(uuid)
let filePath = fileURL.path
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
let file = FileHandle(forWritingAtPath: filePath)!
let boundary = UUID().uuidString
let newline = "rn"
do {
let partName = "file"
let data = try Data(contentsOf: uploadParam.sourceURL)
// Write boundary header
var header = ""
header += "--(boundary)" + newline
header += "Content-Disposition: form-data; name="(partName)"; filename="(uploadParam.fileName)"" + newline
for (key, value) in uploadParam.params {
header += "Content-Disposition: form-data; name="(key)" + newline
header += newline
header += value + newline
}
header += "Content-Type: (uploadParam.mimeType)" + newline
header += newline
let headerData = header.data(using: .utf8, allowLossyConversion: false)
// Write data
file.write(headerData!)
file.write(data)
// Write boundary footer
var footer = ""
footer += newline
footer += "--(boundary)--" + newline
footer += newline
let footerData = footer.data(using: .utf8, allowLossyConversion: false)
file.write(footerData!)
file.closeFile()
let contentType = "multipart/form-data; boundary=(boundary)"
var urlRequest = URLRequest(url: uploadParam.destinationUrl)
urlRequest.httpMethod = "POST"
urlRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
return (urlRequest, fileURL)
} catch {
debugPrint("Error generating url request")
}
return nil
}
- 上传文件
func uploadFile(request: UploadRequest) {
if let reqPath = requestAndPath(for: uploadRequest) {
let task = session.uploadTask(with: reqPath.request,
fromFile: reqPath.filePath)
task.resume()
}
}
当我调用uploadFile
方法时,委托didSendBodyData
被调用一次,控件转到didCompleteWithError
,错误为nil。但是该文件并没有上传到s3 bucket中。可能是什么问题?
我可以使用Alamoire上传文件,但由于Alamoire不支持后台上传,我想回退到URLSession
使用Alamofire(默认(上传
AF.upload(multipartFormData: { multipartFormData in
for (key, value) in uploadRequest.params {
if let data = value.data(using: String.Encoding.utf8, allowLossyConversion: false) {
multipartFormData.append(data, withName: key)
}
}
multipartFormData.append(
uploadRequest.sourceURL,
withName: "File",
fileName: uploadRequest.fileName,
mimeType: uploadRequest.mimeType
)
}, with: urlRequest).responseData { response in
if let responseData = response.data {
let strData = String(decoding: responseData, as: UTF8.self)
debugPrint("Response data (strData)")
} else {
debugPrint("Error is (response.error)")
}
}.uploadProgress { progress in
debugPrint("Progress (progress)")
}
我对请求主体进行了更改,并将数据写入文件,并使用后台会话使用uploadTask(with:fromFile:)
方法。当应用程序处于后台时,上传完成后将调用urlSessionDidFinishEvents(forBackgroundURLSession:)
。
private func requestAndPath(for
uploadParam: UploadRequest) -> (request: URLRequest,
filePath: URL)? {
let uuid = UUID().uuidString
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let fileURL = directoryURL.appendingPathComponent(uuid)
let filePath = fileURL.path
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
let file = FileHandle(forWritingAtPath: filePath)!
let boundary = generateBoundary()
let lineBreak = "rn"
var body = Data()
for (key, value) in uploadParam.params {
body.append("--(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name="(key)"(lineBreak + lineBreak)")
body.append("(value + lineBreak)")
}
do {
let data = try Data(contentsOf: uploadParam.sourceURL)
body.append("--(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name="File"; filename="(uploadParam.fileName)"(lineBreak)")
body.append("Content-Type: (uploadParam.mimeType + lineBreak + lineBreak)")
body.append(data)
body.append(lineBreak)
body.append("--(boundary)--(lineBreak)")
file.write(body)
file.closeFile()
var urlRequest = URLRequest(url: uploadParam.destinationUrl)
urlRequest.setValue("multipart/form-data; boundary=(boundary)", forHTTPHeaderField: "Content-Type")
urlRequest.httpMethod = "POST"
return (urlRequest, fileURL)
} catch {
debugPrint("Error getting request")
}
return nil
}
func generateBoundary() -> String {
return UUID().uuidString
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
参考:https://stackoverflow.com/a/58246456/696465