Upload Multipart files Completion block - alamofire

I'm using alamofire5 beta and I can't find the encodingResult that was used in previous versions.
This is my code function:
static func postComplexPictures(complexId: String, pictures: [UIImage], completion:#escaping (DataResponse<Data?>) -> Void) {
let url = K.ProductionServer.baseURL + "/api/v1/complex/" + complexId + "/pictures"
let token: String = UserDefaults.standard.string(forKey: "Token") ?? ""
let bearerToken: String = "Bearer " + token
let bundleId: String = Bundle.footballNow.bundleIdentifier!
let headers: HTTPHeaders = [HTTPHeaderField.authentication.rawValue: bearerToken,
HTTPHeaderField.contentType.rawValue: ContentType.multipart.rawValue,
HTTPHeaderField.bundleIdentifier.rawValue: bundleId]
AF.upload(multipartFormData: { (multipartFormData) in
for image in pictures {
if let imageData = UIImageJPEGRepresentation(image, 0.5) {
multipartFormData.append(imageData, withName: "pictures[\(index)]", fileName: "picture", mimeType: "image/jpeg")
}
}
}, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers).response(completionHandler: completion)
}
The .response actually calls my block, but it returns too quick for the images to be uploaded and I don't have a reference to the uploading status of the images.
Any thoughts?
Thanks!

I'm happy to say that there is no encoding result in Alamofire 5! Instead, failures in multipart encoding, and the async work required to encode it, are now part of the same request path as everything else. So you'll get any errors in your response calls, just like any other request. So if your request is finishing quickly, check the error, as the multipart encoding may have failed.

Related

Getting error while uploading image using multipart request in Alamofire

xcode 10.3
swift 4.2
iPhone app
I'm hitting a post type API to upload image along with some string data from a form. I'm getting response result as success but there is an error parameters which indicates: CredStore - performQuery - Error copying matching creds. Error=-25300
I have tried doing some research and found that I've problem with URLCredentialStorage but I don't have any Credentials in API.
I looked at CredStore Perform Query error, I'm having very similar issue but can't find my solution there.
{
let url = route.asURL(constants: constants)
let parameters = route.asParameters(constants: constants)
let headers: HTTPHeaders = [
"authorization-name": APISession.token,
"Content-Type": "multipart/form-data"
]
Log(request: URLRequest(url: url))
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
if let strValue = value as? String, let valueData = strValue.data(using: .utf8) {
multipartFormData.append(valueData, withName: key)
}
if let imgValue = value as? UIImage, let imgData = imgValue.jpegData(compressionQuality: 0.5) {
multipartFormData.append(imgData, withName: key, mimeType: "image/jpg")
}
}
}, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers) { (result) in
print(result)
switch result{
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Preogress is: ", progress.fractionCompleted)
})
upload.responseJSON { response in
print("Succesfully uploaded")
switch response.result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}
}
I'm getting response like this,
success(request: 2019-08-12 13:07:04.224913-0400 project name[65508:460860] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = htps;
"r_Attributes" = 1;
sdmn = "url";
srvr = "url";
sync = syna;
}
so I'm confused why I'm getting success response with error parameters in that.
Not sure but I think authorization-name key present in headers is incorrect. It should be Authorization.

Alamofire.download by post with parameters not working

Hello I am doing download file by post with parameters. But server can't receive post parameters.
But if i do same thing with get with url parameters. Everything works fine.
Almofire.request also works fine by post with parameters. But only Almofire.download by post with parameter does not work.
Why Alamofire.download does not send paramters by post method ??
var sourceStringURL : String = "\(tmp_url)download"
let destination: DownloadRequest.DownloadFileDestination =
{
_, _ in
let fileURL = URL(fileURLWithPath: destPath)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(sourceStringURL, method: .post, parameters: ["id": idStr, "var": varStr], encoding: JSONEncoding.default, headers: nil, to: destination)
.downloadProgress
{
progress in
var tmpPercent : Int = Int(progress.fractionCompleted*100 / 1.0)
}
.response
{
response in
if let error = response.error
{
print(error)
}
else
{
//success
}
}
Server receives post request correctly with Retrofit library in Android.
I just found that if i change JSONEncoding.default to URLEncoding.default.
It works fine.

Get server response message from error

My server (CakePHP) is responding like so:
$this->response->statusCode('400');
$this->response->type('json');
$this->response->body(json_encode(array('message' => 'Bookmark already exists')));
The Postman output looks like what you would expect:
{"message":"Bookmark already exists"}
The problem is that I cannot find a way to access this message from the failure handler (Alamofire 3.1.3 + SwiftyJSON 2.3.2)
Alamofire.request(.POST...
.validate()
.responseJSON { response in
switch response.result {
case .Success(_):
// All good
case .Failure(let error):
// Status code 400
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result)
I cannot find a way to cast response.data to JSON as a I simply get nil and the result returns just FAILURE.
Is there a way to access this server message from the failure handler ?
The data is not parsed in the .Failure case per the Alamofire 3.0 migration guide. However, server data is still available in response.data and can be parsed.
Below should work to parse this manually:
Alamofire.request(.POST, "https://example.com/create", parameters: ["foo": "bar"])
.validate()
.responseJSON { response in
switch response.result {
case .Success:
print("Validation Successful")
case .Failure(_):
var errorMessage = "General error message"
if let data = response.data {
let responseJSON = JSON(data: data)
if let message: String = responseJSON["message"].stringValue {
if !message.isEmpty {
errorMessage = message
}
}
}
print(errorMessage) //Contains General error message or specific.
}
}
}
This uses SwiftyJSON which provides the JSON struct to convert NSData. Parsing NSData to JSON can done without SwiftyJSON, answered here.
Another cleaner option might be to write a Custom Response Serializer.
A method with the router and no SwiftyJSON:
Alamofire.request(APIRouter.Register(params: params)).validate().responseJSON { response in
switch response.result {
case .Success(let json):
let message = json["clientMessage"] as? String
completion(.Success(message ?? "Success"))
case .Failure(let error):
var errorString: String?
if let data = response.data {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
errorString = json["error"]
}
}
completion(.Error(errorString ?? error.localizedDescription))
}
}
I have used the following lines to read the response body from a Alamofire request.
Alamofire.request(.POST, serveraddress, headers: headers, encoding: .JSON)
.response{ request, response, data, error in
let responseData = String(data: data!, encoding: NSUTF8StringEncoding)
print(responseData)
}
With this body I can get my custom server response errormessage.
best regards
For Alamofire 4.0 and above :
Try this
response.response?.statusCode
url : "YOUR-URL"
parameters: "YOUR PARAMETER DICTIONARY"
headers: "YOUR HEADER DICTIONARY"
I am using SwiftyJSON for JSON Parsing
A sample request is here :
func createPostRequestWith(path: String?,
parameters: [String : Any]? = nil,
success : #escaping (Any?) -> (),
failure : #escaping (NSError) -> ()) {
if !(Alamofire.NetworkReachabilityManager()?.isReachable)! {
let error = NSError(domain: "", code: -1003, userInfo: nil)
failure(error)
} else {
guard let url = path else { return }
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: createCurrentHeader()).validate(statusCode: 200..<300).responseJSON {
response in
switch response.result {
//Remove loader here either after parsing or on error
case .success(let data):
success(data)
case .failure(let error):
print(error)
if let responseData = response.data {
var parsedResponseData = JSON.init(data: responseData)
let customError = NSError(domain: parsedResponseData["message"].stringValue, code: response.response?.statusCode ?? 555, userInfo: nil)
failure(customError as NSError)
} else {
failure(error as NSError)
}
}
}
}
}

Missing argument for parameter #2 in call to Alamofire.request(URLRequestConvertible)

Trying to pass an Authentication header in, using the recommended approach of a custom URLRequestConvertible.
So here is my URLRequestConvertible object that conforms to the protocol:
class SecureJSONRouter : URLRequestConvertible {
var type: String
var token: String
var parameters: [String: AnyObject]
init(typevar: String, tokenvar: String, parametersvar: [String: AnyObject]) {
type = typevar
token = tokenvar
parameters = parametersvar
}
var URLRequest: NSURLRequest {
let URL = NSURL(string: da_url)!
let URLRequesting = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(type))
let encoding = Alamofire.ParameterEncoding.JSON
URLRequesting.setValue(token, forHTTPHeaderField: "Authorization")
return encoding.encode(URLRequesting, parameters: parameters).0
}
}
It's basically a place to store a few things, like a bit of URL, and a token, and a way to create a NSURLRequest with a full URL, the JSON parameters, and the header field for authorization.
When I try to call it like this:
let myUrlRequest: URLRequestConvertible = SecureJSONRouter(typevar: "locations", tokenvar: token!, parametersvar: parameters)
Alamofire.request(myUrlRequest).response{ (req, resp, data, error) in
if (error != nil) {
println(req)
println(resp)
} else {
println("saved \(data)")
}
}
it won't compile, gives me a "Missing argument for parameter #2 in call" error, at the request line.
Any ideas?
PS: I did have my SecureJSONRouter thingy as the recommended Enum instead of a Class, but I got the same error. I was looking at the protocol definition, and figured there's no reason it can't be a simpler (class) in my case, so I changed it. Still the same error.
I think there's a few possible culprits here. First off, are you sure you're calling Alamofire and not AlamoFire? I've seen people make that mistake before.
As for the sample code you posted, it didn't compile for a few different reasons. I couldn't reproduce exactly the same compiler errors you were seeing, but here's a slightly modified version of you original post that does compile.
class SecureJSONRouter : URLRequestConvertible {
var type: String
var token: String
var parameters: [String: AnyObject]
init(typevar: String, tokenvar: String, parametersvar: [String: AnyObject]) {
type = typevar
token = tokenvar
parameters = parametersvar
}
var URLRequest: NSURLRequest {
let URL = NSURL(string: "http://httpbin.org")!
let URLRequesting = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(type))
let encoding = Alamofire.ParameterEncoding.JSON
URLRequesting.setValue(token, forHTTPHeaderField: "Authorization")
return encoding.encode(URLRequesting, parameters: parameters).0
}
}
Then here's an example of calling your SecureJSONRouter.
let token: String? = "my_fancy_token"
let parameters: [String: AnyObject] = ["sample_parameter": "sample_parameter_value"]
let myUrlRequest: URLRequestConvertible = SecureJSONRouter(typevar: "locations", tokenvar: token!, parametersvar: parameters)
Alamofire.request(myUrlRequest).response{ (req, resp, data, error) in
if (error != nil) {
println(req)
println(resp)
} else {
println("saved \(data)")
}
}
That is compiling with Xcode 6.1.1 against the Alamofire 1.1.3 release. Hope that helps!

PhoneGap FileTransfer with HTTP basic authentication

I'm attempting to upload a file from PhoneGap to a server using the FileTransfer method. I need HTTP basic auth to be enabled for this upload.
Here's the relevant code:
var options = new FileUploadOptions({
fileKey: "file",
params: {
id: my_id,
headers: { 'Authorization': _make_authstr() }
}
});
var ft = new FileTransfer();
ft.upload(image, 'http://locahost:8000/api/upload', success, error, options);
Looking over the PhoneGap source code it appears that I can specify the authorization header by including "headers" in the "params" list as I've done above:
JSONObject headers = params.getJSONObject("headers");
for (Iterator iter = headers.keys(); iter.hasNext();)
{
String headerKey = iter.next().toString();
conn.setRequestProperty(headerKey, headers.getString(headerKey));
}
However, this doesn't seem to actually add the header.
So: is there a way to do HTTP basic auth with PhoneGap's FileTransfer, for both iPhone and Android?
You can add custom headers by adding them to the options rather than the params like so:
authHeaderValue = function(username, password) {
var tok = username + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
};
options.headers = {'Authorization': authHeaderValue('Bob', '1234') };
The correct location for the headers array is as an immediate child of options. options->headers. Not options->params->headers. Here is an example:
//**************************************************************
//Variables used below:
//1 - image_name: contains the actual name of the image file.
//2 - token: contains authorization token. In my case, JWT.
//3 - UPLOAD_URL: URL to which the file will be uploaded.
//4 - image_full_path - Full path for the picture to be uploaded.
//***************************************************************
var options = {
fileKey: "file",
fileName: 'picture',
chunkedMode: false,
mimeType: "multipart/form-data",
params : {'fileName': image_name}
};
var headers = {'Authorization':token};
//Here is the magic!
options.headers = headers;
//NOTE: I creaed a separate object for headers to better exemplify what
// is going on here. Obviously you can simply add the header entry
// directly to options object above.
$cordovaFileTransfer.upload(UPLOAD_URL, image_full_path, options).then(
function(result) {
//do whatever with the result here.
});
Here is the official documentation: https://github.com/apache/cordova-plugin-file-transfer
You can create a authorization header yourself. But you can also enter the credentials in the url like this:
var username = "test", password = "pass";
var uri = encodeURI("http://"+username + ':' + password +"#localhost:8000/api/upload");
See FileTransfer.js for the implementation (line 45):
function getBasicAuthHeader(urlString) {
var header = null;
// This is changed due to MS Windows doesn't support credentials in http uris
// so we detect them by regexp and strip off from result url
// Proof: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/a327cf3c-f033-4a54-8b7f-03c56ba3203f/windows-foundation-uri-security-problem
if (window.btoa) {
var credentials = getUrlCredentials(urlString);
if (credentials) {
var authHeader = "Authorization";
var authHeaderValue = "Basic " + window.btoa(credentials);
header = {
name : authHeader,
value : authHeaderValue
};
}
}
return header;
}