AFHTTPSessionManager get error response from server [duplicate] - ios

I've been using AFNetworking 2.0 in my app.
I've noticed that if my web-service returns a 500 status code I do not get the body of the response.
Here is an example of my php code
try
{
$conn = new PDO( "sqlsrv:server=$serverName;Database = $database", $uid, $pwd);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $conn;
}
catch( PDOException $e )
{
$response->status(500);
echo( "Connection Error: " . $e->getMessage() );
}
If I use a simple rest client this is an example of a response body.
Connection Error: SQLSTATE[08001]: [Microsoft][SQL Server Native Client 11.0]SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF].
However this seems to be the only response I can get from AFNetworking
Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (JSON text did not start with array or object and option to allow fragments not set.) UserInfo=0x15e58fa0 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
This is the part of my objective-c code that does this.
...} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",error.description);
}];
Is there a way I can get the response body?
Edit: More code for clarification
Below is part of my subclass of AFHTTPSessionManager
#implementation MSMAMobileAPIClient
+ (MSMAMobileAPIClient *)sharedClient {
static MSMAMobileAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MSMAMobileAPIClient alloc] initWithDefaultURL];
});
return _sharedClient;
}
- (id)initWithDefaultURL {
return [self initWithBaseURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://%#/mamobile/index.php/" ,[[NSUserDefaults standardUserDefaults] stringForKey:#"serviceIPAddress"]]]];
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
self.responseSerializer = [AFCompoundResponseSerializer compoundSerializerWithResponseSerializers:#[[AFJSONResponseSerializer serializer], [AFHTTPResponseSerializer serializer]]];
return self;
}
I tried setting the response serializer to a AFCompoundResponseSerializer but it didn't seem to make a difference
Below is an example of a subclass that I call the Librarian.
-(void)searchForItemWithString:(NSString *)searchString withCompletionBlock:(arrayBlock)block {
self.inventorySearchBlock = block;
NSDictionary *parameters = #{#"query": searchString};
[[MSMAMobileAPIClient sharedClient] GET:#"inventory/search" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (!responseObject) {
NSLog(#"Error parsing JSON");
} else {
//do stuff with the json dictionary that's returned..
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#",error.description);
}];
}

UPDATE: I have created a github repository to contain the latest code I am using. All changes will be posted there. https://github.com/Hackmodford/HMFJSONResponseSerializerWithData
The answer comes from this issue on github.
https://github.com/AFNetworking/AFNetworking/issues/1397
gfiumara is the dev who came up with this. I have only slightly modified his subclass of AFJSONResponseSerializer to include an actual string instead of the NSData
//MSJSONResponseSerializerWithData.h
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = #"JSONResponseSerializerWithDataKey";
#interface MSJSONResponseSerializerWithData : AFJSONResponseSerializer
#end
// MSJSONResponseSerializerWithData.m
#import "MSJSONResponseSerializerWithData.h"
#implementation MSJSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (nil);
}
return ([super responseObjectForResponse:response data:data error:error]);
}
#end
Here is an example of how I use it in the failure block.
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",[error.userInfo objectForKey:#"JSONResponseSerializerWithDataKey"]);
}];

You need to use AFCompoundSerializer to tell the AFNetworking framework how to process all of the possible responses it could receive. By default it will only try to map JSON. A compound serializer will work through the serializers until it finds one that doesn't raise an error.
You want to use:
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers
on AFCompoundResponseSerializer (in AFURLResponseSerialization.h).
You need to pass an array of serializers that can handle the response. One of the serializers in the array should be an instance of AFHTTPResponseSerializer to handle your error responses.

If you include my category in your project, it's as simple as the following:
[mySessionManager POST:#"some-api" parameters:params success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
...
} failure:^(NSURLSessionDataTask *task, NSError *error) {
id responseObject = error.userInfo[kErrorResponseObjectKey];
... do something with the response ...
}];
Here's the code for my category. It swizzles AFURLSessionManager to inject a shim into the completion handler. The shim puts the response into the NSError's userInfo.
https://gist.github.com/chrishulbert/35ecbec4b37d36b0d608

Related

difficulty understanding objective c code

i have been trying to implement the follow code , but I am having a hard time understanding the following code:
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
{
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
}
success(routes);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
}
I tried learning about blocks but I still can not understand what is going on here. Can you provide me a step by step explanation of the code ?
actually here used for webserviceCall
step-1
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
// here pass the one NSString and get the response using NSArray and failure using NSString
step-2
// here used AFNEtworking for call web service
//request block
[self AFManagerObject] -- NSObject class for AFNetworking method place.
POST:GET_ROUTES --> post is default function of request Type, GET_ROUTES --> your Macro class for Request URL
parameters --> send the parameter to server
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
/*********** success response serlize and store into Array**********/
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
// this is your NSObject class for save the details ,
}
success(routes);
/************** success stop **********/
}
/*********** error if request is fail ************/
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
/*********** error if request is stop ************/
I think you need to read a little more about callbacks https://en.m.wikipedia.org/wiki/Callback_(computer_programming) and blocks https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html and https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html
Basically the method send a POST request and as you know it needs some time for the request to be sent to the server and for the server to respond. You don't want in this time your application to be freezed, so 2 callbacks are used, 1 for success case and 1 for failure case. A block callback is just a block of code that you want to be executed later, when the server will respond back, being a success or failure.

AFHTTPSessionManager - get unserialized/raw response body (NSData?)

I've subclassed AFHTTPSessionManager according to the recommended best practice for iOS 8 (in place of AFHTTPOperationManager, which I was using before).
I can grab the NSHTTPURLResponse from the task (except that has no body, only headers), and the callback returns the serialized responseObject which is fine.
Sometimes I need to log the response as a string or display it in a text field - there doesn't appear to be a way to do this natively using SessionManager? OperationManager allowed you to reference the raw response as an NSString:
operation.responseString;
I suppose I could stringify the serialized requestObject, but that seems like a lot of unnecessary overhead, and won't help if the response object is invalid JSON.
Here's my subclassed singleton:
#implementation MyAFHTTPSessionManager
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
And then to make a simple GET (which I've added to a block method), I can do:
[[MyAFHTTPSessionManager sharedManager] GET:_url parameters:queryParams success:^(NSURLSessionDataTask *task, id responseObject) {
completion(YES, task, responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
completion(NO, task, nil, error);
}];
You can accomplish this by creating a custom response serializer that records the data and serializes the response using the standard response serializer, combining both the raw data and parsed object into a custom, compound response object.
#interface ResponseWithRawData : NSObject
#property (nonatomic, retain) NSData *data;
#property (nonatomic, retain) id object;
#end
#interface ResponseSerializerWithRawData : NSObject <AFURLResponseSerialization>
- (instancetype)initWithForwardingSerializer:(id<AFURLResponseSerialization>)forwardingSerializer;
#end
...
#implementation ResponseWithRawData
#end
#interface ResponseSerializerWithRawData ()
#property (nonatomic, retain) forwardingSerializer;
#end
#implementation ResponseSerializerWithRawData
- (instancetype)initWithForwardingSerializer:(id<AFURLResponseSerialization>)forwardingSerializer {
self = [super init];
if (self) {
self.forwardingSerializer = forwardingSerializer;
}
return self;
}
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
id object = [self.forwardingSerializer responseObjectForResponse:response data:data error:error];
// TODO: could just log the data here and then return object; so that none of the request handlers have to change
if (*error) {
// TODO: Create a new NSError object and add the data to the "userInfo"
// TODO: OR ignore the error and return the response object with the raw data only
return nil;
} else {
ResponseWithRawData *response = [[ResponseWithRawData alloc] init];
response.data = data;
response.object = object;
return response;
}
}
#end
Then set this serializer on your session manager:
#implementation MyAFHTTPSessionManager
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
instance.responseSerializer = [[ResponseSerializerWithRawData alloc] initWithForwardingSerializer:instance.responseSerializer];
});
return instance;
}
Now in your completion handler you will get an instance of ResponseWithRawData:
[[MyAFHTTPSessionManager sharedManager] GET:_url parameters:queryParams success:^(NSURLSessionDataTask *task, id responseObject) {
ResponseWithRawData *responseWithRawData = responseObject;
NSLog(#"raw data: %#", responseWithRawData.data);
// If UTF8 NSLog(#"raw data: %#", [[NSString alloc] initWithData:responseWithRawData.data encoding:NSUTF8StringEncoding]);
// TODO: do something with parsed object
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
I just whipped this up without compiling/testing, so I will leave it to you to debug and fill in the gaps.
You can access the “data” object directly from AFNetworking by using the “AFNetworkingOperationFailingURLResponseDataErrorKey” key so there is no need for subclassing the AFJSONResponseSerializer. You can the serialize the data into a readable dictionary. Here is some sample code to get JSON Data :
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Here is code to Get Status code in Failur block
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( #"success: %d", r.statusCode );

GET request using AFNetworking and saving response

I am doing a simple GET request with AFNetworking
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Once I have made the request I want to be able to access the responseObject from any other method in the class.
I want to be able to save the responseObject so I can do something like display the output in a tableview.
It's common to creat object models that will be represented by JSON. When you get the response you would then parse the data into the models. The approach we use is to return the response to the requester through a completion block. You don't have to parse the JSON into strongly typed objects, but it really is helpful long term. It's probably a good idea to farm out the network request operations into a separate class (called a service) as well. This way you can instantiate a new service and get notified through a completion block that it is finished. For example your service's request signature could look like this:
typedef void(^HelloWorldCompletionHandler)(NSString *helloWorld, NSError *error);
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler;
// implementation
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
id JSONResponse = [operation responseObject];
if (operation.error) {
completionHandler(nil, error);
} else {
// parse the response to something
id parserResult = [self parseJSONResponse:JSONResponse];
completionHandler(parserResult, nil);
}
}];
This way you'll know when the network request is complete, and you can set the data you want on a property within your class. Then you could call tableView.reloadData in order to use the data in your table.
All that code would go into a service type class. I like to organize my services by responsibility. I don't know how many different data calls you make, but we have several for our project. If for instance you were making a weather app you could potentially organize by Current Conditions, Daily Forecasts, and Hourly Forecasts. I would make a service for each one of these requests. Say I created a CurrentConditionsService. The header would look something like this:
typedef void(^CurrentConditionsCompletionHandler)(CurrentConditions *currentConditions, NSError *error);
#interface CurrentConditionsService : NSObject
// locationKey is some unique identifier for a city
+ (instancetype)serviceWithLocationKey:(NSString *)locationKey;
- (void)retrieveCurrentConditionsWithCompletionHandler:(CurrentConditionsCompletionHandler)completionHandler;
#end
Then in my implementation file I would make the request and invoke the given completion handler like I demonstrated above. This pattern can be followed by many different services to the point where all your services could inherit from a base class that handles the request/response portions. Then your subclasses could override specific methods and handle/parse the data appropriately based on type.
If you go the route of parsing the JSON responses into model objects, all your parsers will need to conform to a protocol. This way in your super class it doesn't matter what the concrete implementation of your parser is. You supply the super class with a concrete implementation and all it knows how to do is invoke the parser and return the response.
An example JSON parser protocol would look like this:
#protocol AWDataParser <NSObject>
#required
- (id)parseFromDictionary:(NSDictionary *)dictionary;
- (NSArray *)parseFromArray:(NSArray *)array;
#end
And invoking it in your services super class:
- (id)parseJSONResponse:(id)JSONResponse error:(NSError **)error {
NSAssert(self.expectedJSONResponseClass != nil, #"parseResponse: expectedJSONResponseClass cannot be nil");
NSAssert(self.parser != nil, #"parseResponse: parser cannot be nil");
id parserResult = nil;
if (![JSONResponse isKindOfClass:self.expectedJSONResponseClass]) {
//handle invalid JSON reponse object
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:#{#"Invalid JSON type": [NSString stringWithFormat:#"expected: %#, is: %#",self.expectedJSONResponseClass, [JSONResponse class]]}];
}
} else {
if (self.expectedJSONResponseClass == [NSArray class]) {
parserResult = [self.parser parseFromArray:JSONResponse];
}else {
parserResult = [self.parser parseFromDictionary:JSONResponse];
}
if (!parserResult) {
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:nil];
}
}
}
return parserResult;
}
Use this approach:
NSURL *COMBINAT = [[NSURL alloc] initWithString:#"http://someapi.com/hello.json"];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
COMBINAT];
[self performSelectorOnMainThread:#selector(savedata:) withObject:data waitUntilDone:YES];
});
then simply call:
- (void)savedata:(NSData *)responseData {
NSError* error;
NSLog(#"Answer from server %#", responseData);
  // ... your code to use responseData
}
Just create a property:
#property(nonatomic, strong) id savedResponseObject;
and set it in the success handler of the request:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
self.savedResponseObject = responseObject;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#", error);
}];
Then you will be able to access it from other places in your class by referencing:
self.savedResponseObject

AFNetworking 500 response body

I've been using AFNetworking 2.0 in my app.
I've noticed that if my web-service returns a 500 status code I do not get the body of the response.
Here is an example of my php code
try
{
$conn = new PDO( "sqlsrv:server=$serverName;Database = $database", $uid, $pwd);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $conn;
}
catch( PDOException $e )
{
$response->status(500);
echo( "Connection Error: " . $e->getMessage() );
}
If I use a simple rest client this is an example of a response body.
Connection Error: SQLSTATE[08001]: [Microsoft][SQL Server Native Client 11.0]SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF].
However this seems to be the only response I can get from AFNetworking
Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (JSON text did not start with array or object and option to allow fragments not set.) UserInfo=0x15e58fa0 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
This is the part of my objective-c code that does this.
...} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",error.description);
}];
Is there a way I can get the response body?
Edit: More code for clarification
Below is part of my subclass of AFHTTPSessionManager
#implementation MSMAMobileAPIClient
+ (MSMAMobileAPIClient *)sharedClient {
static MSMAMobileAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MSMAMobileAPIClient alloc] initWithDefaultURL];
});
return _sharedClient;
}
- (id)initWithDefaultURL {
return [self initWithBaseURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://%#/mamobile/index.php/" ,[[NSUserDefaults standardUserDefaults] stringForKey:#"serviceIPAddress"]]]];
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
self.responseSerializer = [AFCompoundResponseSerializer compoundSerializerWithResponseSerializers:#[[AFJSONResponseSerializer serializer], [AFHTTPResponseSerializer serializer]]];
return self;
}
I tried setting the response serializer to a AFCompoundResponseSerializer but it didn't seem to make a difference
Below is an example of a subclass that I call the Librarian.
-(void)searchForItemWithString:(NSString *)searchString withCompletionBlock:(arrayBlock)block {
self.inventorySearchBlock = block;
NSDictionary *parameters = #{#"query": searchString};
[[MSMAMobileAPIClient sharedClient] GET:#"inventory/search" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (!responseObject) {
NSLog(#"Error parsing JSON");
} else {
//do stuff with the json dictionary that's returned..
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#",error.description);
}];
}
UPDATE: I have created a github repository to contain the latest code I am using. All changes will be posted there. https://github.com/Hackmodford/HMFJSONResponseSerializerWithData
The answer comes from this issue on github.
https://github.com/AFNetworking/AFNetworking/issues/1397
gfiumara is the dev who came up with this. I have only slightly modified his subclass of AFJSONResponseSerializer to include an actual string instead of the NSData
//MSJSONResponseSerializerWithData.h
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = #"JSONResponseSerializerWithDataKey";
#interface MSJSONResponseSerializerWithData : AFJSONResponseSerializer
#end
// MSJSONResponseSerializerWithData.m
#import "MSJSONResponseSerializerWithData.h"
#implementation MSJSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (nil);
}
return ([super responseObjectForResponse:response data:data error:error]);
}
#end
Here is an example of how I use it in the failure block.
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#",[error.userInfo objectForKey:#"JSONResponseSerializerWithDataKey"]);
}];
You need to use AFCompoundSerializer to tell the AFNetworking framework how to process all of the possible responses it could receive. By default it will only try to map JSON. A compound serializer will work through the serializers until it finds one that doesn't raise an error.
You want to use:
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers
on AFCompoundResponseSerializer (in AFURLResponseSerialization.h).
You need to pass an array of serializers that can handle the response. One of the serializers in the array should be an instance of AFHTTPResponseSerializer to handle your error responses.
If you include my category in your project, it's as simple as the following:
[mySessionManager POST:#"some-api" parameters:params success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
...
} failure:^(NSURLSessionDataTask *task, NSError *error) {
id responseObject = error.userInfo[kErrorResponseObjectKey];
... do something with the response ...
}];
Here's the code for my category. It swizzles AFURLSessionManager to inject a shim into the completion handler. The shim puts the response into the NSError's userInfo.
https://gist.github.com/chrishulbert/35ecbec4b37d36b0d608

AFNetworking AFHTTPClient AFJSONRequestOperation not using JSON Operation

I followed this Screencast... http://nsscreencast.com/episodes/6-afnetworking
My singleton AFHTTPClient code is...
+ (MyClient *)sharedInstance
{
static dispatch_once_t once;
static MyClient *myClient;
dispatch_once(&once, ^ { myClient = [[MyClient alloc] initWithBaseURL:[NSURL URLWithString:MyBaseURL]];});
return myClient;
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
// these are not actual values but I am setting default headers.
[self setDefaultHeader:#"sdfg" value:#"4"];
[self setDefaultHeader:#"std" value:#"3"];
[self setDefaultHeader:#"reg" value:#"5"];
[self setDefaultHeader:#"yu" value:#"1"];
[self setDefaultHeader:#"xv" value:#"3"];
[self setDefaultHeader:#"hmm" value:#"5"];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
return self;
}
Then I'm executing it like...
[[MyClient sharedInstance] getPath:#"blah.php" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSMutableArray *stats = [NSMutableArray array];
// it crashes on the next line because responseObject is NSData
for (NSDictionary *dictionary in responseObject) {
CCStatistic *stat = [[CCStatistic alloc] initWithDictionary:dictionary];
[stats addObject:stat];
}
self.stats = stats;
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error retrieving!");
NSLog(#"%#", error);
}];
It all works fine. I've intercepted it with Charles and it is sending the correct request and receiving the correct JSON except the operation is not a JSON operation.
So the responseObject is NSData not the JSON object that I was expecting.
An I missing any config to use the JSON operation?
The line of code that in your case is deciding whether the request is processable by a JSON operation is the following:
return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:#"Accept"])];
As explained here by Mattt Thompson (the author of AFNetworking) you have to set the Accept header for the request to application/json.
This is not intuitive and it's highly implementation-dependent, but it works.
Add
[self setDefaultHeader:#"Accept" value:#"application/json"];
to your client initialization and it should be ok, regardless of the path extension.
OK, so there is a check in the AFNetworking code that throes out the JSON operation if the request url extension is not json.
Because mine is php it's throwing it out.
Changing the code worked.
My Change
In the AFJSONRequestOperation method...
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
return [[[request URL] pathExtension] isEqualToString:#"json"] || [super canProcessRequest:request];
}
I changed it to...
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
// added php to the request valid path extensions.
return [[[request URL] pathExtension] isEqualToString:#"json"] || [super canProcessRequest:request] || [[[request URL] pathExtension] isEqualToString:#"php"];
}
It's not really a bug per se more like a random syntax style of the requests that are open to me.
Is it normal to have .php requests returning JSON?

Resources