Working on an iOS project I found out that Alamofire changes the Content-Transfer-Encoding of a multipart/form-data field depending on the field value.
Goal
Update an image with some informations (unicode strings) to an endpoint.
Details
I define the form's fields values as follows:
let formFields: [String: String] = [
"key": _model
]
I upload the entire form (image + informations) as follows:
Alamofire.upload(
multipartFormData: { multipartFormData in
// 1. The form's fields
for (key, value) in formFields {
guard let byteString = value.data(using: String.Encoding.utf8, allowLossyConversion: false) else { continue }
multipartFormData.append(byteString, withName: key)
}
// 2. The picture
let fileName = "file.jpeg"
multipartFormData.append(pictureData, withName: ParameterKeys.BikeImage, fileName: fileName, mimeType: "image/jpeg")
},
to: _url,
method: .post,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _): /* Called when the encoding succeds. */
upload.validate(statusCode: 200..<300)
upload.uploadProgress { progress in // main queue by default
progressHandler(progress)
}
upload.response { (response) in
responseHandler(response)
}
case .failure(let error): /* Called when the encoding fails. */
failureHandler(error)
}
}
)
Behaviour 0
_model is "Standard" ✅
multipartFormData.append(byteString, ...) appends "5374616e64617264" in the form ✅
The server gets "Standard" without Content-Transfer-Encoding ⁉️
Behaviour 1
_model is "Ström Bike" ✅
multipartFormData.append(byteString, ...) appends "537472c3b66d2042696b65" in the form ✅
The server gets "Str=C3=B6m Bike" with Content-Transfer-Encoding: quoted-printable ⁉️
Behaviour 2
_model is "Abräcadabra" ✅
multipartFormData.append(byteString, ...) appends "416272c3a463616461627261" in the form ✅
The server gets "QWJyw6RjYWRhYnJh" with Content-Transfer-Encoding: base64 ⁉️
Conclusion
On the server, I cannot determine the format of the form data. Is there a way to specify the type of encoding to use for the form fields?
Thank you in advance.
Related
I'm using Alamofire class for api calling. Api is working properly in Postman
please check below two screenshots for reference,
in first image data is passing inside raw body
in second image data is passing inside Headers field
now i'm using this code to call API
//for params i'm sending below parameters
//["phoneNumber":"911234567890", "countryCode" : "91"]
let headers: HTTPHeaders = [
"deviceId" : deviceId,
"osVersion": osVersion,
"deviceType": deviceType,
"resolution":resolution,
"buildNumber":buildNumber]
AF.request(strURL, method: .post, parameters: params, encoding: JSONEncoding.default, headers:headers).responseData { (response) in
switch response.result {
case .success(let data):
do {
//let asJSON = try JSONSerialization.jsonObject(with: data)
let asJSON = try JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
// success
print(asJSON)
let res : NSDictionary = (asJSON as AnyObject) as! NSDictionary
successBlock(res)
} catch { // error
print("decoding error:\n\(error)")
}
case .failure(let error):
print(error)
failure(error)
}
}
in all other project above code is working fine for api calling, but here i'm getting below error
{ code = 500; data = ""; message = "Failed to convert value of type 'java.lang.String' to required type 'java.util.Locale'; nested exception is java.lang.IllegalArgumentException: Locale part "en;q=1.0" contains invalid characters"; …………………… NamedValueMethodArgumentResolver.java:125)\n\t... 97 more\n"; status = "INTERNAL_SERVER_ERROR"; timestamp = "01-03-2022 07:55:20"; }
i've try several methods like URLEncoding.default, passing custom header, create custom raw request & passed header inside but nothing works,
AnyOne have solution for this issue?
Thanks in Advance.
As it is throwing error related to Local, I think some language is defined and it doesn't accept * for Accept-Language header, try sending "en" in the header Accept-Language.
Check subtags for language:
http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry :
Test Code:
func callAPI() {
let params: Parameters = ["phoneNumber":"911234567890", "countryCode" : "91"]
let headers = [
"deviceId" : "jdhcbkerfjkr",
"osVersion": "3.2.3",
"deviceType": "ANDROID",
"resolution": "122x122",
"buildNumber": "3.2.1",
"Accept-Language": "en"]
AF.request("[Test-URL]",
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: HTTPHeaders.init(headers)).response { response in
print(String(data: response.data!, encoding: .utf8)!)
}
}
We're having this weird issue. The "+" is removed at the DB once we sent our request.
But if I print the response.data, I can see that the "+" is there (using Alamofire 4.9.1)
What could be the possible reason why the "+" is removed?
Here's how I set the request and the headers:
For the sake of future readers, application/x-www-form-urlencoded requests must be “percent encoded”, replacing the + character with %2B. But there are actually lots of characters that might need encoding, so it is generally best to use the Alamofire request method with Parameters, which takes care of all of those details for you. E.g. in Alamofire 4.9.1:
let headers: HTTPHeaders = [
"Authorization": authorization
]
let parameters: Parameters = [
"grant_type": "password",
"username": username,
"password": password
]
Alamofire.request(url, method: .post, parameters: parameters, headers: headers).responseJSON { response in
switch response.result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
Or in Alamofire 5 and later, one would use AF rather than Alamofire:
AF.request(url, method: .post, parameters: parameters, headers: headers).responseJSON { response in
switch response.result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
See the Alamofire usage document: POST Request With URL-Encoded Parameters
If you really want to build the httpBody of the URLRequest yourself, rather than just percent-encoding only the + character, you might consider a more generalized percent-coding routine, as shown in JSON request sending empty data. The idea is the same as what Alamofire’s Parameters, namely build the httpBody from a dictionary.
Turns out, the "+" is not being escaped properly. So I had to replace "+" with "%2B"
let requestString = "grant_type=password&username=" + encryptedTokenUsernameBody + "&password=" + encryptedTokenPasswordBody
let encodedString = requestString.replacingOccurrences(of: "+", with: "%2B")
I'm currently developing an application using iOS 10 and Swift 3 and Alamofire 4
The purpose of this application is to upload a PDF file generated previously.
The PDF generation is working perfectly and the file is created.
However the upload doesn’t work…
I received a success response but the file is not uploaded.
My server response
Multi part Content-Type => multipart/form-data; boundary=alamofire.boundary.56958be35bdb49cb
Multi part Content-Length => 293107
Multi part Content-Boundary => alamofire.boundary.56958be35bdb49cb
responses
SUCCESS: {
uploadedFiles = (
{
details = " Key=Content-Disposition - values=[form-data; name=\"pdfDocuments\"] length=8";
storedFileName = "/var/www/pdf/17/009/22/TMP104150531290406.tmp";
type = PDF;
uploadedDate = 1483999296701;
uploadedFileName = UnknownFile;
}
);
}
end responses
I’m using multi-part to upload my file as Data as you can see here
File url is fine.
I have searched on SO but didn’t find any solution working…
Here you can see my Controller
Alamofire.upload(
multipartFormData: {
multipartFormData in
if let urlString = urlBase2 {
let pdfData = try! Data(contentsOf: urlString.asURL())
var data : Data = pdfData
multipartFormData.append(data as Data, withName:"test.pdf", mimeType:"application/pdf")
for (key, value) in body {
multipartFormData.append(((value as? String)?.data(using: .utf8))!, withName: key)
}
print("Multi part Content -Type")
print(multipartFormData.contentType)
print("Multi part FIN ")
print("Multi part Content-Length")
print(multipartFormData.contentLength)
print("Multi part Content-Boundary")
print(multipartFormData.boundary)
}
},
to: url,
method: .post,
headers: header,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { response in
print(" responses ")
print(response)
print("end responses")
onCompletion(true, "Something bad happen...", 200)
}
case .failure(let encodingError):
print(encodingError)
onCompletion(false, "Something bad happen...", 200)
}
})
Thanks in advance for the help.
Regards
I have just found my solution to fix this bug.
I have forgot a parameter for the file name.
multipartFormData.append(pdfData, withName: "pdfDocuments", fileName: namePDF, mimeType:"application/pdf")
Thanks for the help.
I'm unable to approach the API that has been offered to me in the proper way for it to give me the response I'm looking for. I've been using Swift and Alamofire for a while but this is the first time I've to upload images using multipart/form-data. I'm able to upload images using Postman but I'm unable to get the same message send out by my application using the Alamofire framework.
My Swift code:
func postFulfilWish(wish_id: Int, picture : UIImage, completionHandler: ((AnyObject?, ErrorType?) -> Void)) {
var urlPostFulfilWish = Constant.apiUrl;
urlPostFulfilWish += "/wishes/";
urlPostFulfilWish += String(wish_id);
urlPostFulfilWish += "/fulfill/images" ;
let image : NSData = UIImagePNGRepresentation(UIImage(named: "location.png")!)!
Alamofire.upload(.POST, urlPostFulfilWish, headers: Constant.headers, multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: image, name: "file")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
//This is where the code ends up now
//So it's able to encode my message into multipart/form-data but it's not doing it in the correct way for the API to handle it
debugPrint(response)
}
case .Failure(let encodingError):
print(encodingError)
}
}
)
}
In case it is not already answered, recently I had the same problem using Alamofire to upload an Image using form-data.
I was able to upload the image using Postman exactly as it's shown in this post, but no able to do it using Alamofire in my app.
You need to check two things, first of all, the name of the file that the server is expecting, and second the method used to append the body part in the multipartFormData closure.
This two methods were not working in my case -
multipartFormData.appendBodyPart(data: imageData, name: "file")
this one neither
multipartFormData.appendBodyPart(data: imageData, name: "file", fileName: name)
But on this one worked splendid -
multipartFormData.appendBodyPart(data: imageData, name: "file", fileName: "file.jpeg", mimeType: "image/jpeg")
The problem basically is that the server can't find the file with the expected name.
I hope this help someone to save time wondering why it's not working.
You are doing debugPrint(response). You presumably should do another switch response.result { ... } and see if you got .Success or .Failure as the result of the request, and if success, you'd look at the response object contents (or if failure, look at the failure error). You need to look at that result to diagnose whether it was successful or not.
Alamofire.upload(.POST, urlPostFulfilWish, headers: Constant.headers, multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: image, name: "file")
}) { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
switch response.result {
case .Success(let value):
print(value)
case .Failure(let error):
print(error)
}
}
case .Failure(let encodingError):
print(encodingError)
}
}
I recently got a 404 from the server when posting a multipart request along with parameters in the body. I was using a UIImagePickerController (the delegate for which returns a UIImage) and I then sent up the PNG representation of it.
This only occurred for files that were JPEG on disk. Strangely this issue seems to only affect multipart requests that also had parameters in the body. It worked fine when the API endpoint didn't require anything else.
My guess is that there is something weird going on along the line of JPEG -> UIImage -> PNG representation that results in some sort of problem which oddly only seems to manifest itself in multipart requests that also have parameters in the body. Might be some special characters in there that makes the server not recognise the request and just return a 404.
I ended up fixing it by sending up the UIImageJPEGRepresentation of the selected image instead of UIImagePNGRepresentation, and no such errors.
I believe the question is outdated already but for as long as there is no answer accepted try the following:
multipartFormData.appendBodyPart(data: imageData, name: "name", fileName: "filename", mimeType: mimeType)
I'm trying to upload image data from iOS using Alamofire to an Express server with Multer. req.file is undefined, and req.body is in the form { file: <bytes> }. There is no error message, but the file does not appear. Here is my code:
var bodyParser = require('body-parser')
var multer = require('multer')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.post('/api/photos/upload', function(req, res) {
var upload = multer({ dest: 'public/images/content/'}).single('file')
upload(req, res, function(err) {
if (err) {
console.log("Error uploading file: " + err)
return
}
// req.file = req.body
console.log(req.body) // form fields
console.log(req.file) // form file
})
res.json('yeah')
})
On iOS:
let url = fullURL("api/photos/upload")
Alamofire.upload(.POST, url, multipartFormData: { multipartFormData in
if let image = image {
if let imageData = UIImageJPEGRepresentation(image, 0.5) {
multipartFormData.appendBodyPart(data: imageData, name: "file")
}
}
}, encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
switch response.result {
case .Success:
print("success")
case .Failure(let error):
print(error)
}
}
case .Failure(let encodingError):
print(encodingError)
}
})
This has puzzled me for hours, any help is greatly appreciated!
UPDATE
An HTML form worked fine through the Express endpoint, so it's definitely a problem with the request Alamofire is sending. I've tried a bunch of examples of uploading with Alamofire, but they all send the same incorrect request. There must be a way to make the same request as an HTML form but with Alamofire.
ANOTHER UPDATE
I'm now just using busboy-connect and it's working well, and with a lot more flexibility.
I was just able to get this working. It turns out that you have to specify the fileName and the mimeType using Alamofire in order for multer to pick up the upload on the server end. So, your code for adding the image should look something like this:
if let image = image {
if let imageData = UIImageJPEGRepresentation(image, 0.5) {
multipartFormData.appendBodyPart(data: imageData, name: "file", fileName: "fileName.jpg", mimeType: "image/jpeg")
}
}
Your issue is likely caused by not using multer as a middleware:
var upload = multer({ dest: 'public/images/content/'})
app.post('/api/photos/upload', upload.single('file'), function(req, res) {
// req.file should be populated now
})
In express, you can add as many middlewares as you need:
app.post('/path',
middleware1,
middleware2,
middleware3,
...,
function(req, res) {
// All middlewares has been executed
})
you know, there is difference between the method multipartFormData.append(value.data, withName: name, fileName: filename, mimeType: mimeType) and multipartFormData.append(value.data, withName: key).
when you use the former, multer will take it as req.file、the latter as req.body.