Accessing the JSON response in the failure block of AFNetworking 2 - ruby-on-rails

With AFNetworking 2, when you're handling a failure in the failure block, how do you access the content returned from the server? In my case I'm posting to a Rails app that is returning:
{"number":["is already taken"]}
That's what I get if I use curl like this: curl -X POST -d "sales_order[number]=12345" http://localserver.dev/api/v1/sales_orders.json
I'm trying to get the same JSON within AFNetworking 2. After reading all over SO I managed to get access to some response header information by examining [error userInfo] inside of my failure block.
Does anyone know how I can access the {"number":["is already taken"]} from inside of the failure block?
This is my block currently:
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Sales Order Failure");
NSDictionary *userInfo = [error userInfo];
for(NSString *key in [userInfo allKeys]) {
NSLog(#"%# - %#", key, [userInfo objectForKey:key]);
}
}];

This is a known design deficiency in AFNetworking 2.x, and has been discussed here with some workarounds.

Related

AFNetworking to REST API returns 401

I'm trying to use a REST API in my iOS app. I know it works because I can make the login request once. Every subsequent request fails with a 401 error. Even if I delete the app from the simulator it still can't be called again until I change the simulator type to one that I haven't used before (i.e. iPad 2, iPhone6, etc.). I can also use a service like https://www.hurl.it to make the same request with the same parameters as many times as I'd like. I'm using AFNetworking and AFHTTPRequestOperationManager. What am I doing wrong?
self.manager = [[AFHTTPRequestOperationManager alloc]initWithBaseURL:[NSURL URLWithString:#"https://api.mydomain.com/"]];
[self.manager POST:#"services/json/user/login" parameters:#{#"username":#"USERNAME", #"password":#"PASSWORD"} success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (![responseObject isKindOfClass:[NSDictionary class]]) { return; }
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSDictionary* json = responseObject;
self.sessionName = json[#"session_name"];
self.sessionId = json[#"sessionid"];
[defaults setObject:self.sessionName forKey:#"SessionNameKey"];
[defaults setObject:self.sessionId forKey:#"SessionIDKey"];
if (completion) { completion(); }
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Session request failed: %#", error.userInfo);
}];
If first login called success, you should get some access_token which you use to send along with any subsequent calls.
If API has basic authentication, then you need to pass credentials in HTTP header.
To set credentials in header you can use following methods:
[self.manager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"username" password:#"password"];
(Unauthorised) 401 means you don't have access to the service. Make sure you are trying with correct credentials. Apparently there's nothing wrong with the code.
Update for iOS9 with Swift 2.0 using Bearer authorization
I had the same problem, actually the sessionManager.session.configuration.HTTPAdditionalHeaders get overwritten by the requestSerializer on iOS9, which cause the 401 Access denied error.
Code that was working on iOS8, but not on iOS9:
sm.session.configuration.HTTPAdditionalHeaders = ["Authorization" : "Bearer \(token!)"]
New code for iOS9
sm.requestSerializer.setValue("Bearer \(token!)", forHTTPHeaderField: "Authorization")

iOS: How would a put request work in this scenario?

I am using AFNetworking and trying to figure out how to use PUT requests properly. This is what the API document has given me (just an easy example).
curl -X PUT -d '{"problem":[{"problem":"text"}]}'
"https://api.website.com/form?apiKey={apiKey}"
Params is
'{"problem":[{"problem":"text"}]}' while urlStr is https://api.website.com/form?apiKey={apiKey} If you need any more information please let me know.
Here is my put request being executed.
- (void) executePutRequest : (NSString *) url params : (NSString *) params
{
NSString *urlStr = [NSString stringWithFormat:#"%#?apikey=%#", url,apiKey];
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:
NSASCIIStringEncoding];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager PUT:urlStr parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
[operation setUserInfo:userinfo];
SBJsonParser *jsonparser = [SBJsonParser new];
id result = [jsonparser objectWithString:[operation responseString]];
if ( self.delegate != nil && [self.delegate respondsToSelector:finishSelector] ) {
[self.delegate performSelector:finishSelector withObject:result];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[operation setUserInfo:userinfo];
if ( self.delegate != nil && [self.delegate respondsToSelector:failSelector] ) {
[self.delegate performSelector:failSelector withObject:[operation error]];
}
}];
}
What am I doing wrong? I am getting a 400 error (bad request). This is what my log looks like.
Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: bad request (400)" UserInfo=0x7aee9a40 {com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x7aeea400>
AFNetworking is meant to do some of the redundant work for you, including serializing and deserializing output to server and input from server respectively. Your code implies that you are trying to do it manually, too, which is incorrect.
You have params set to be an NSString *. Just pass it the NSDictionary * it expects. e.g. NSDictionary *params = #{ #"problem": #[ #{ #"problem": #"text" }]};, from your example.
If you enter the success block, then responseObject should be deserialized into the format you have configured. IIRC, the default response format is JSON -- but look it up. You don't need to further process the response -- it's been done for you. For example, if the JSON body is something like { "cat": { "says": "meow" } }, then simply cast the object to an NSDictionary; e.g. NSDictionary *catDict = (NSDictionary *)responseObject;
Did you or your team write the web service? Do you know what the expected request and response format is?
If you have to further configure the request and response serializers, you can do so in your manager's constructor (so subclass the manager, and make your own). For e.g,
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
... would configure the client to send JSON to the server, and expect JSON back from the server.

AFNetworking Error - 'Invalid type in JSON write (NSConcreteData)' - when uploading audio file

I am attempting to use AFNetworking to upload an audio file to Cloudinary through a POST call. I have been using AFNetworking to send POST calls to my own server with ease but this is the first time I have tried sending an NSData object through AFNetworking which is where I think the problem lies and am not exactly sure how I need to adjust the call to get it to work.
My Code
I need to send 4 parameters through the post call to the Cloudinary service which I place in to a dictionary....
NSData *audioFile = [[NSData alloc]initWithContentsOfURL:_audioRecorder.url];
NSMutableDictionary *myParam = [NSMutableDictionary dictionary];
[myParam setValue:#(nowTime) forKey:#"timestamp"];
[myParam setValue:audioFile forKey:#"file"];
[myParam setValue:api_key forKey:#"api_key"];
[myParam setValue:finalSignature forKey:#"signature"];
[cloudManager setUpDataCall:#"/upload" withParameters:myParam];
The setUpDataCall goes to a subclass of AFHTTPSessionManager with this code. This is the rest of the post URL if you were curious #"https://api.cloudinary.com/v1_1/karmo/raw"
- (void)setUpDataCall:(NSString *)callURL withParameters:(NSDictionary *)parameters {
[self POST:callURL parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if ([self.delegate respondsToSelector:#selector(cloudManager:didReturnData:)]){
//Success :)
}
}failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([self.delegate respondsToSelector:#selector(cloudManager:didFailWithError: )]) {
//Error :(
}
}];
}
The Error
I get this error when I try to send the file Invalid type in JSON write (NSConcreteData).
When I add an exception breakpoint it identifies this
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
Which is in this method in the AFURLRequestSerialization.m class.
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
I have seen a few other stackoverflow questions like the one below but I am not sure if those are still relevant as the code from those posts seem to throw errors in the code as I am guessing AFNetworking has been updated since those posts.
AFNetworking Uploading a file
Any code help or examples would be greatly appreciated. Thanks a bunch for taking the time.
Have you tried the suggestions as explained in the following post's comments?
Error with NSJSONSerialization - Invalid type in JSON write (Menu)

With AFNetworking 2.0 how can I test the size of my POST request with JSON?

Not sure if this is possible but I basically need to check the size of the json in bytes or mb before it is sent to the server.
Does anyone know how to do this?
My code for sending the data to the server is:
[manager POST:#"http://10.1.0.119:9000/sync" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"String Result: %#",operation.responseString);
NSLog(#"Return JSON: %#", responseObject);
NSLog(#"-------------------------------");
[self executeUpdates:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"String Result: %#",operation.responseString);
NSLog(#"Error: %#", error);
}];
This code works 100% except where the JSON file is to big for the server. The server generates a 413 Error code but this does not get returned to my app on the client. My app continues as normal when the code is generated on the server and all I get back is a NULL JSON response with no error codes which I thought was weird.
I know the max size of the JSON file can be set on the server but this has obvious issues if i can't detect the error on the client side.
Hope you guys can help.
EDIT
The JSOn is blank because no data was send and received. But it is proper json as confirmed by jsonlint.com so how do you measure the size of the sending json? send from iPad to server? on the iPad?
JSON:
Sending JSON:{"teachers" : [],"syncheader" : {"servertimesync" : "","deviceid" : "4E48DBF8-F616-4AB2-A926-95F197DCCD83"},"classes" : [],"learners" : []}
Return JSON: {classes = ();learners = ();schools = (); syncheader = {deviceid = "4E48DBF8-F616-4AB2-A926-95F197DCCD83"; servertimesync = 20131129122854;}; teachers = ();}
You could convert it to NSData object like this and get the total bytes from there.
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myDict options:0 error:NULL];
NSUInteger total_bytes = [jsonData length];

Why is success block being called with incorrect path?

I accidentally mistyped the post path and noticed that although it's being wrong, the success block is called:
[[APIClient sharedInstance]
postPath:#"api_url"
parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Result: Success %#",[responseObject description]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//handle error
NSLog(#"Result: Failure + %#",error.userInfo);
}];
Of course the data are not being sent to server and the transaction is not processed, but I want to know why it's not the failure block which is supposed to be called in case the path is wrong? Thanx.
Failure is called if the requestOperation has an associated error after finishing. Reasons for an error include the response having the incorrect Content-Type, not having an acceptable status code (2XX range, by default), or an error processing the downloaded data.
Why your server returned a 200 response with the correct content type is a question only something you can determine.

Resources