Swift 如何从iOS(客户端)通过URL请求发送文件
这是我上传文件的RESTAPI-Swift 如何从iOS(客户端)通过URL请求发送文件,swift,file-upload,python-requests,urlsession,urlrequest,Swift,File Upload,Python Requests,Urlsession,Urlrequest,这是我上传文件的RESTAPI- @api.route('/update_profile_picture', methods=['POST']) def update_profile_picture(): if 'file' in request.files: image_file = request.files['file'] else: return jsonify({'response': None, 'error' : 'NO File foun
@api.route('/update_profile_picture', methods=['POST'])
def update_profile_picture():
if 'file' in request.files:
image_file = request.files['file']
else:
return jsonify({'response': None, 'error' : 'NO File found in request.'})
filename = secure_filename(image_file.filename)
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image_file.save(image_path)
try:
current_user.image = filename
db.session.commit()
except Exception as e:
return jsonify({'response': None, 'error' : str(e)})
return jsonify({'response': ['{} profile picture update successful'.format(filename)], 'error': None})
上面的代码在我使用postman测试时运行良好,但是在postman中我可以设置一个file对象。
然而,当我尝试从iOS应用程序上传时,它会给我错误信息-
NO File found in request
这是我上传图片的swift代码-
struct ImageFile {
let fileName : String
let data: Data
let mimeType: String
init?(withImage image: UIImage, andFileName fileName: String) {
self.mimeType = "image/jpeg"
self.fileName = fileName
guard let data = image.jpegData(compressionQuality: 1.0) else {
return nil
}
self.data = data
}
}
class FileLoadingManager{
static let sharedInstance = FileLoadingManager()
private init(){}
let utilityClaas = Utility()
func uploadFile(atURL urlString: String, image: ImageFile, completed:@escaping(Result<NetworkResponse<String>, NetworkError>)->()){
guard let url = URL(string: urlString) else{
return completed(.failure(.invalidURL))
}
var httpBody = Data()
let boundary = self.getBoundary()
let lineBreak = "\r\n"
let contentType = "multipart/form-data; boundary = --\(boundary)"
httpBody.append("--\(boundary + lineBreak)")
httpBody.append("Content-Disposition: form-data; name = \"file\"; \(lineBreak)")
httpBody.append("Content-Type: \(image.mimeType + lineBreak + lineBreak)")
httpBody.append(image.data)
httpBody.append(lineBreak)
httpBody.append("--\(boundary)--")
let requestManager = NetworkRequest(withURL: url, httpBody: httpBody, contentType: contentType, andMethod: "POST")
let urlRequest = requestManager.urlRequest()
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error as? NetworkError{
completed(.failure(error))
return
}
if let response = response as? HTTPURLResponse{
if response.statusCode < 200 || response.statusCode > 299{
completed(.failure(self.utilityClaas.getNetworkError(from: response)))
return
}
}
guard let responseData = data else{
completed(.failure(NetworkError.invalidData))
return
}
do{
let jsonResponse = try JSONDecoder().decode(NetworkResponse<String>.self, from: responseData)
completed(.success(jsonResponse))
}catch{
completed(.failure(NetworkError.decodingFailed))
}
}
dataTask.resume()
}
private func boundary()->String{
return "--\(NSUUID().uuidString)"
}
}
extension Data{
mutating func append(_ string: String) {
if let data = string.data(using: .utf8){
self.append(data)
}
}
}
在ImageLoaderViewController
中,选择要发送以上载的图像
class ImageLoaderViewController: UIViewController {
@IBOutlet weak var selectedImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func selectImage(){
if selectedImageView.image != nil{
selectedImageView.image = nil
}
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .photoLibrary
imagePicker.delegate = self
self.present(imagePicker, animated: true, completion: nil)
}
@IBAction func uploadImageToServer(){
if let image = imageFile{
DataProvider.sharedInstance.uploadPicture(image) { (msg, error) in
if let error = error{
print(error)
}
else{
print(msg!)
}
}
}
}
func completedWithImage(_ image: UIImage) -> Void {
imageFile = ImageFile(withImage: image, andFileName: "test")
}
}
extension ImageLoaderViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage{
picker.dismiss(animated: true) {
self.selectedImageView.image = image
self.completedWithImage(image)
}
}
picker.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
错误在于每次在生成新UUID的代码中调用
boundary()
函数,但资源必须只有一个UUID。因此,只需为您的资源生成一次UUID,然后在需要的地方插入此值:
。。。
设boundary=boundary()
let contentType=“多部分/表单数据;边界=\(边界)”
...
设置多部分表单数据内容可能很棘手。尤其是在组合请求主体的许多部分时,可能会出现细微的错误
内容类型请求标头值:
let contentType=“多部分/表单数据;边界=-->(边界)”
这里,边界参数前面不应加前缀“-”。
此外,根据相应的RFC删除任何不明确允许的WS。
此外,将边界参数括在双引号中可使其更加健壮,而且不会造成任何伤害:
let contentType=“多部分/表单数据;边界=\”\(边界)\“”
初始正文:
httpBody.append(“--\(边界+换行符)”)
这是身体的开始。在主体之前,请求头被写入主体流。每个标头都用一个CRLF完成,在最后一个标头之后,必须写入另一个CRLF。嗯,我很肯定,URL请求将确保这一点。尽管如此,还是值得使用一个工具来检查这一点,该工具显示了在导线上书写的字符。
否则,将前面的CRLF添加到边界(从概念上讲,它无论如何都属于该边界,并且不会造成任何伤害):
httpBody.append(“\(换行符)--\(边界)\(换行符)”)
内容配置:
httpBody.append(“内容处置:表单数据;名称=\“文件\”;\(换行符)”)
在此,您也可以删除其他WS:
httpBody.append(“内容处置:表单数据;名称=\“文件\”;\(换行符)”)
或者,您可能需要提供文件名
参数和值。不过,这不是强制性的
关闭边界
这里没有错误:
httpBody.append(lineBreak)
httpBody.append("--\(boundary)--")
但您可能需要明确,前面的CRLF属于边界:
httpBody.append("\(lineBreak)--\(boundary)--")
关闭边界后的字符将被服务器忽略
编码
extension Data{
mutating func append(_ string: String) {
if let data = string.data(using: .utf8){
self.append(data)
}
}
}
通常不能返回utf8编码的字符串并将其嵌入HTTP请求正文的许多不同部分。HTTP协议的许多部分只允许有限的字符集。在许多情况下,不允许使用UTF-8。您必须在相应的RFC中查找详细信息-这很麻烦,但也很有启发性;)
参考资料:
这是我在library的帮助下,使用
多部分表单从IOS客户端上传文件的方法
let url=“url here”
let头:HTTPHeaders=[
“授权”:“此处为持票人代币”,
“接受”:“应用程序/x-www-form-urlencoded”
]
AF.upload(multipartFormData:{(multipartFormData)在
multipartFormData.append(imageData,名称为:“image”,文件名为:“image.png”,mimeType:“image/png”)
},to:url,method:.post,headers:headers).validate(状态码:200..是多部分的好例子。我认为构建多部分可能有问题:
let body = NSMutableData()
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
if fileURLs != nil {
if fileKeyName == nil {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "If fileURLs supplied, fileKeyName must not be nil"])
}
for fileURL in fileURLs! {
let filename = fileURL.lastPathComponent
guard let data = NSData(contentsOfURL: fileURL) else {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to open \(fileURL.path)"])
}
let mimetype = NSURLSession.mimeTypeForPath(fileURL.path!)
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(fileKeyName!)\"; filename=\"\(filename!)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.appendData(data)
body.appendString("\r\n")
}
}
body.appendString("--\(boundary)--\r\n")
return body
嗯……这不是Swift。你应该从你的问题中删除[Swift]
标记。“我正在尝试使用URLSession。”你有Swift代码吗?你知道邮递员可以为你的请求生成Swift代码吗?这不是漂亮的代码,但你可能会使用/受到启发的代码?跳转到错误的标记可能会令人沮丧。因此,请删除[Swift]我想作者最终想要Swift代码,但没有表现出任何努力,只是用另一种语言显示他/她的服务器代码。我需要一些帮助或资源提示,告诉我如何编写iOS客户端代码以使用URLSession发送图像。我尝试了你的建议,但也没有效果。@Natasha你必须在同一个re中使用相同的边界quest!所以,最好将它定义为一些let值,并在多部分数据中使用它,正如iUrii所建议的;)每个请求都有一个新的边界是可以的。它将在内容类型请求标头中定义。它必须是一系列不在有效负载内发生的字符。然后,指定的边界必须在内容类型请求头中定义的多部分中使用。我用建议的更新更新了我的帖子。我用你所有的建议更新了帖子,但仍然没有成功。到目前为止,我们发现了一些错误,这些错误实际上阻止了发送有效的请求。我不确定我们是否发现了所有问题。您现在可能希望在创建URLRequest后将其记录(在单元测试中)。还打印出UTF-8解码的正文,并检查它是否是有效的多部分/表单数据正文。您可以在单元测试中检查这一点。还可能使用Charles Proxy之类的工具来检查此工具是否识别请求。如果这是确定的,您需要检查您的服务器。请注意,我们不知道您的服务器需要什么,而“请求中找不到文件”是您的自定义错误处理。此外,您可以尝试一个可以处理多部分/表单数据的模拟服务器。发送一个简短的文件来检查您的客户方法。旁注:在这个级别上,联网可能很棘手,因此我对出现的问题并不感到惊讶:)大多数开发人员在必须发送多部分/表单数据时使用客户端库。;)没有阿拉莫菲尔。我想处理URLSession。
let url = "url here"
let headers: HTTPHeaders = [
"Authorization": "Bearer Token Here",
"Accept": "application/x-www-form-urlencoded"
]
AF.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(imageData, withName: "image" ,fileName: "image.png" , mimeType: "image/png")
}, to: url, method: .post ,headers: headers).validate(statusCode: 200..<300).response { }
let body = NSMutableData()
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
if fileURLs != nil {
if fileKeyName == nil {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "If fileURLs supplied, fileKeyName must not be nil"])
}
for fileURL in fileURLs! {
let filename = fileURL.lastPathComponent
guard let data = NSData(contentsOfURL: fileURL) else {
throw NSError(domain: NSBundle.mainBundle().bundleIdentifier ?? "NSURLSession+Multipart", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unable to open \(fileURL.path)"])
}
let mimetype = NSURLSession.mimeTypeForPath(fileURL.path!)
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(fileKeyName!)\"; filename=\"\(filename!)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.appendData(data)
body.appendString("\r\n")
}
}
body.appendString("--\(boundary)--\r\n")
return body