Hackmodford
Hackmodford

Reputation: 3970

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);

    }];

}

Upvotes: 8

Views: 7958

Answers (3)

Chris
Chris

Reputation: 40661

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

Upvotes: 0

Hackmodford
Hackmodford

Reputation: 3970

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"]); 
    }];

Upvotes: 14

Wain
Wain

Reputation: 119041

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.

Upvotes: 0

Related Questions