how to send file through urlRequest from iOS(client-end) - swift

Here is my REST API for uploading file-
#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})
The above code works fine as I tested with postman but in postman I can set a file object.
However, when I try to upload from iOS app, it gives me the error-
NO File found in request
Here is my swift code to upload image-
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)
}
}
}
Also here is the NetworkRequest struct-
class NetworkRequest{
var url: URL
var httpBody: Data?
var httpMethod: String
var contentType = "application/json"
init(withURL url:URL, httpBody body:Data, contentType type:String?, andMethod method:String) {
self.url = url
self.httpBody = body
self.httpMethod = method
if let contentType = type{
self.contentType = contentType
}
}
func urlRequest()->URLRequest{
var request = URLRequest(url: self.url)
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
request.httpBody = self.httpBody
request.httpMethod = self.httpMethod
return request
}
}
In The ImageLoaderViewController, an image is selected to be sent to be uploaded.
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)
}
}

The mistake is that you call boundary() function each time in your code that generates you new UUID but the resource must have a single one. So just generate UUID for your resource once and then insert this value where you need:
...
let boundary = boundary()
let contentType = "multipart/form-data; boundary = \(boundary)"
...

Setting up a multipart form-data content can be tricky. Especially there can be subtle errors when combining the many parts of the request body.
Content-Type request header value:
let contentType = "multipart/form-data; boundary = --\(boundary)"
Here, the boundary parameter should not be preceded with the prefix "--".
Also, remove any WS that are not explicitly allowed according the corresponding RFC.
Furthermore, enclosing the boundary parameter in double quotes makes it more robust and never hurts:
let contentType = "multipart/form-data; boundary=\"\(boundary)\""
Initial body:
httpBody.append("--\(boundary + lineBreak)")
This is the start of the body. Before the body, the request headers are written into the body stream. Each header is completed with a CRLF, and after the last header another CRLF must be written. Well, I am pretty sure, URLRequest will ensure this. Nonetheless, it might be worth checking this with a tool that shows the characters written over the wire.
Otherwise, add a preceding CRLF to the boundary (which conceptually belongs to the boundary anyway, and it does not hurt also):
httpBody.append("\(lineBreak)--\(boundary)\(lineBreak)")
Content-Disposition:
httpBody.append("Content-Disposition: form-data; name = \"file\"; \(lineBreak)")
Here, again you may remove the additional WS:
httpBody.append("Content-Disposition: form-data; name=\"file\"; \(lineBreak)")
Optionally, you may want to provide a filename parameter and a value. This is not mandatory, though.
Closing boundary
There's no error here:
httpBody.append(lineBreak)
httpBody.append("--\(boundary)--")
But you might want to make it clear, that the preceding CRLF belongs to the boundary:
httpBody.append("\(lineBreak)--\(boundary)--")
Characters after the closing boundary will be ignored by the server.
Encoding
extension Data{
mutating func append(_ string: String) {
if let data = string.data(using: .utf8){
self.append(data)
}
}
}
You cannot generally return utf8 encoded strings and embed this into the many different parts of a HTTP request body. Many parts of the HTTP protocol allow only a restricted set of characters. In many cases, UTF-8 is not allowed. You have to look-up the details in the corresponding RFCs - which is cumbersome, but also enlightened ;)
References:
RFC 7578, Definition of multipart/form-data

This is my way to upload a file from IOS Client using multipart form , with the help of Alamofire library
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 { }

Here is nice example of Multipart. I think it could be something wrong with building multipart:
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

Related

Upload form-data with SwiftUI and Express backend

I want to upload some user data for register new users and I want to upload a profile picture. I'v tried with this code but it doesn't work, on my backend im just receiving a request with nothing like a form-data inside.
This is my swift code
func UserRegisterRequest(firstname: String, lastname: String, username: String, email: String, password: String, image: UIImage , completionHandler: #escaping (ReturnMessage) -> Void) {
let parameters = ["name": "MyTestFile123321",
"id": "12345"]
// MARK: URL Components
var components = URLComponents()
components.scheme = "https"
components.host = "my-url"
components.path = "/register"
//MARK: Create URL
guard let url = components.url else {
print("Invalid URL")
return
}
guard let mediaImage = Media(withImage: image, forKey: "profilePicture") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
//create boundary
let boundary = generateBoundary()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
//call createDataBody method
let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
request.httpBody = dataBody as Data
print(request.httpBody)
URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.customFormatter)
let response = try decoder.decode(ReturnMessage.self, from: data)
completionHandler(response)
}catch(let error) {
print(error.localizedDescription)
}
} else {
print("No Data")
}
}.resume()
}
func generateBoundary() -> String {
return "Boundary-\(NSUUID().uuidString)"
}
func createDataBody(withParameters params: Parameters?, 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 {
print(photo)
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
}
I don't know if this is the right way to do it but any help is welcome.
thanks for you helping me guys

Swift https POST request for the PlantNet API

Currently I'm trying to program an https post request for the Plant Net API (https://my.plantnet.org/account/doc#) in Swift. I want to have one to five images inside my https post request and each image has as parameter organ (in my case this is always "leaf"). When I try to run my application, I get the error code 400 "Invalid multipart payload format". I'm not sure how I can solve this problem.
//
// PlantRecognition.swift
// Happy Plant
//
// Insert several images of the same plant (maximum of 5) and get the result list
//
import Foundation
import UIKit
class PlantRecognition {
//List with images of the plant to be recognized
var imageList: [UIImage] = []
//Api key for the plant net access
var API_KEY: String = "MY_API_KEY"
var plantNetURL: String = ""
init() {
//Set plant net url after class initialization
plantNetURL = "https://my-api.plantnet.org/v2/identify/all?api-key{\(API_KEY)}"
}
func recognizeImages(images: [UIImage]) {
//Try to create the api endpoint url
guard let apiEndpoint = URL(string: "https://my-api.plantnet.org/v2/identify/all?api-key=\(API_KEY)") else {print("Error creating api endpoint"); return }
//Convert the UIImages to jpeg
let imagesJPG = convertImagesToJPEG(images: images)
//Create url request
var request = URLRequest(url: apiEndpoint)
//Set the http method to post (local images)
request.httpMethod = "POST"
//Create boundary which marks the start/end of the request body
let boundary = "Boundary-\(UUID().uuidString)"
//Set value for our specific http header field
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
//Set the mime type to jpg (we will convert our UIImages to jpg)
let mimeType = "image/jpg"
//Create http body
var body = Data()
let boundaryPrefix = " — \(boundary)\r\n"
//Add the parameter to the body (Currently just leaf recognition is possible)
body.append("--\(boundaryPrefix)\r\n")
body.append("Content-Disposition: form-data; name=\"organs\r\n\r\n")
body.append("leaf\r\n")
//Add the images to the body
var filename = String()
for (index,data) in imagesJPG.enumerated(){
filename = "image\(index)"
body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"image[]\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--\r\n")
//Post the request
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 convertImagesToJPEG(images: [UIImage]) -> [Data] {
var dataArray = [Data]()
for i in images {
if let data = i.jpegData(compressionQuality: 1.0) {
dataArray.append(data)
}
}
return dataArray
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}

Cannot create Data for Multi Part Form request

I am trying to send a multipart/form-data request via URLSession I have written a helper function to create the form body as Data for me, however my request keeps failing with a 500 response code.
I was trying to print out the string representation of my request to ensure the body is correct, however simply trying to decode it now is throwing an error as The data couldn’t be read because it isn’t in the correct format.
This leads me to believe something is wrong with how my request is being encoded.
This is essentially my setup -
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/jpeg"
self.filename = "avatar.jpg"
guard let data = image.jpegData(compressionQuality: 0.7) else { return nil }
self.data = data
}
}
typealias Parameters = [String: String]
func createFormData(usingParams params: Parameters?, media: [Media]?, boundary: String) -> Data {
let lineBreak = "\r\n"
var body = Data()
if let parameters = params {
for (key, value) in parameters {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append(string:"\(value + lineBreak)")
}
}
if let media = media {
for photo in media {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.filename)\"\(lineBreak)")
body.append(string:"Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
body.append(photo.data)
body.append(string: lineBreak)
}
}
body.append(string: "--\(boundary)--\(lineBreak)")
return body
}
extension Data {
mutating func append(string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
Which I invoke something like this -
let boundary = UUID().uuidString
guard let profilePic = Media(withImage:image, forKey: "file") else { return }
let data = createFormData(usingParams: nil, media: [profilePic], boundary: boundary)
I have tried to print my data using the following -
do {
print(data)
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(json)
} catch let error {
print(error.localizedDescription)
}
This throws the error mentioned earlier.
I have also tried
let string = String(data: data, encoding: .utf8)
print(string)
This however just returns nil.
I noticed if I remove body.append(photo.data) the data can print out now as JSON, however I am not sure why this matters

Cannot upload zip file with multipart and URLSession Swift

I am experiencing a really strange issue with uploading zip file to s3... Here is my sample code and all details:
private func uploadZipFile(_ photos:URL,_ step:String,_ presignedUrlModel: PresignedUrlModel) {
var uploadingBackgroundTask = UIBackgroundTaskIdentifier(rawValue: 3)
uploadingBackgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
})
let success: () -> Void = {
DispatchQueue.main.async {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
}
}
let failure: (ErrorResponse) -> Void = { error in
DispatchQueue.main.async {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
}
}
let boundary = "Boundary-\(UUID().uuidString)"
let uploadingRequest = URL(string: presignedUrlModel.url)!
var request = APIhelper().getMultipartRequestBody(_type: "POST", _url: uploadingRequest, boundary: boundary)
let body = self.formBackgroundPhotoData(presignedUrlModel, step, photos, boundary)
request.httpBody = body
self.session.configuration.shouldUseExtendedBackgroundIdleMode = true
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let data = data else {
return
}
if let httpResponse = response as? HTTPURLResponse {
if(httpResponse.statusCode==200)
{
success()
}
else if(httpResponse.statusCode>=400 && httpResponse.statusCode<500)
{
//getting 403 errorr
let error = ErrorResponse(message:
self.errorFormatingClass.getLocalizedErrorMessage(identifier: self.errorFormatingClass.ERROR)
,
identifier: self.errorFormatingClass.ERROR)
failure(error)
}
}
}
)
task.resume()
}
I always getting 403 response:AccessDeniedInvalid according to Policy: Policy Condition failed: ["eq", "$Content-Type", "application/zip"]
​
Here is how I form data:
private func formBackgroundPhotoData(_ presignedUrlModel: PresignedUrlModel,_ step: String,_ zip: URL,_ boundary: String) -> Data {
var body = Data()
var key = "key"
var value = presignedUrlModel.key
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "AWSAccessKeyId"
value = presignedUrlModel.AWSAccessKeyId
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "signature"
value = presignedUrlModel.signature
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "policy"
value = presignedUrlModel.policy
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
let filename = zip.lastPathComponent
guard let data = try? Data(contentsOf: zip) else {
#if DEBUG
debugPrint("backgroundupload", "dataisnull")
#endif
return Data()
}
let mimetype = mimeTypeForPath(url: zip)
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimetype)\r\n\r\n")
body.append(data)
body.append("\r\n")
body.append("--\(boundary)--\r\n")
return body
}
Here is multipart request:
public func getMultipartRequestBody(_type: String, _url: URL, boundary: String) ->URLRequest
{
var request = URLRequest(url: _url)
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 300
request.httpMethod = _type
return request
}
Now, I know this tons of code, and perhaps nobody will take a look... All I am asking: please provide an example for uploading zip or images to AWS S3, without adding S3 framework for uploading files..
Our whole team does not know what to do :(

Get value of token in header response of AFNetworking swift

I have some POST webservices that they have token value in header field
How can I read this value ?
I googled but all answers is just for set value in header field
here is my registration function that I call it in another file
and I want access header in didReciveDataFromRegisterWebService and use token for later webservices
func register(phone : String) {
let configuration = URLSessionConfiguration.default
let manager = AFURLSessionManager(sessionConfiguration: configuration)
manager.responseSerializer = AFHTTPResponseSerializer()
let req = AFHTTPRequestSerializer().request(withMethod: "POST", urlString: "http://192.168.1.228:8000/gcab/v1/driver/register", parameters: nil, error: nil)
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let insideDict = ["phone_number" : phone , "device_id" : DeveloperAssistant.getDeviceID() ]
let insideString = insideDict.convertToString()
let jsonString = "data=\(insideString)"
let jsonPostData = jsonString.data(using: .utf8)
req.httpBody = jsonPostData!
let dataTask = manager.dataTask(with: req as URLRequest) { (response : URLResponse, data : Any?, error : Error?) in
if data != nil {
do{...}
else{
let responseData = WebServiceParser.parseDataToObject(data: dict, type: .register) as! RegisterWebServiceResponse
self.registerDelegate?.didReciveDataFromRegisterWebService!(data: responseData, error: nil, response: nil)
}
print("dict info is : \(dictInfo)")
}catch let erro {
print("erro darim \(erro)")
}
print("we have data ")
}else{
print("we don't have data")
}
}
dataTask.resume()
}
Why you are using AFNetworking? I used this lib a lot in Objective-C but since Swift came out I changed to Alamofire because it is written in Swift and very well maintained.
This could help you (not tested!).
Inside your completion handler:
if let response = dataTask.response as? NSHTTPURLResponse {
if let headerFieldValue = response.allHeaderFields["YOUR_HEADER_FIELD"] as? String {
// do somthing
}
}
Here is one short example of how to access one response headerField with Alamofire.
HTTPURLResponse has a Dictionary property called allHeaderFields from which you can access your token value. Here I cast this to String
Alamofire.request("https://serviceURL.de", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
if let headerFieldValue = response.response?.allHeaderFields["YOUR_HEADER_FIELD"] as? String {
// do somthing
}
}