Swift:URLSession实现S3后台上传无错误却失败求助
后台URLSession上传文件至S3存储桶失败排查
我尝试使用URLSession将文件上传到S3存储桶,了解到后台上传需使用uploadTask(with:fromFile:)方法,于是按以下步骤实现:
1. 创建后台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) }()
2. 生成带multipart数据的请求
数据模型
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)? { // 创建空文件并写入请求头、文件内容和请求尾 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 = "\r\n" do { let partName = "file" let data = try Data(contentsOf: uploadParam.sourceURL) // 写入boundary头部 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) // 写入数据 file.write(headerData!) file.write(data) // 写入boundary尾部 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 }
3. 执行文件上传
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且error为nil,但文件并未上传至S3存储桶。我使用Alamofire可正常完成上传(代码如下),但Alamofire不支持后台上传,因此希望改用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)") }
内容的提问来源于stack exchange,提问作者Anish
相关产品推荐
相关产品推荐

