Get JSON with HTTP Authentication with AFNetworking - objective-c

I am trying to get a JSON from my hudson url address and authenticate my (Mac OS X) application using the HTTP Authenticate.
Following the example I'm using:
// AppDelegate.m
- (void) doSomething {
[[CommAPIClient sharedClient] getPath:#"/computer/api/json" parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Name: %#", [responseObject valueForKeyPath:#"totalExecutors"]);
} failure:nil];
}
// CommAPIClient.m
+ (CommAPIClient *) sharedClient {
static CommAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString: [appDelegate.hudsonTextField stringValue]]];
});
return _sharedClient;
}
- (id) initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self){
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
userName = [appDelegate.userTextField stringValue];
password = [appDelegate.passwdTextField stringValue];
[self setAuthorizationHeaderWithUsername:userName password:password];
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
I want to get the computer's list to show in my dropdown, but this two lines does not work together:
[self setAuthorizationHeaderWithUsername:userName password:password];
[self setDefaultHeader:#"Accept" value:#"application/json"];
If I just use the first line, my authetication works, but I receive that error because I try to get a key:
2012-02-03 02:43:57.542 HudsonSlave[7523:707] An uncaught exception was raised
2012-02-03 02:43:57.542 HudsonSlave[7523:707] [<NSConcreteData 0x100850000> valueForUndefinedKey:]: this class is not key value coding-compliant for the key totalExecutors.
2012-02-03 02:43:57.623 HudsonSlave[7523:707] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSConcreteData 0x100850000> valueForUndefinedKey:]: this class is not key value coding-compliant for the key totalExecutors.'
If use the second line, my authentication will return an error 403.
Anyone could help with problem ?
Thanks and apologize for any errors in english.
Thiago

I ran into this issue with the Flickr API and ended up subclassing AFJSONRequestOperation which turned out to be trivial. You need only override the class method defaultAcceptableContentTypes thusly, for example:
+ (NSSet *)defaultAcceptableContentTypes;
{
return [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/plain", nil];
}
Then in my AFHTTPClient subclass, I just register my subclass as the default HTTP operation class:
[self registerHTTPOperationClass:[CCFFlickrJSONRequestOperation class]];
UPDATE 2013-09-09 14-52-47:
The method name is now + (NSSet *)acceptableContentTypes

"Expected content type {( "text/javascript", "application/json", "text/json" )}, got application/javascript"
Just like the error said, the request was expecting one of text/javascript, application/json, or text/json, but the server sent application/javascript (which is an incorrect mime type for JSON).
Either get your server to respond with the correct mime type, or (perhaps easier) manually change the line in AFJSONRequestOperation that specifies which mime types are accessible. There's a less hacky alternative to changing the library code that involves subclassing, but that's probably much more trouble than it's worth in your case.

Actually you can now add more content-types easily:
[AFHTTPRequestOperation addAcceptableContentTypes:[NSSet setWithObjects:#"text/html", nil]];

If it's a one-time thing instead of subclassing the whole AFJSONRequestOperation object you can just set the acceptable content types directly to whatever you need:
AFJSONRequestOperation *operation = blah blah blah
[operation setAcceptableContentTypes:[NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/plain",#"text/html", nil]];
[operation start];

As of AFNetworking 2.0, serialization is no longer a part of RequestOperation classes, and is delegated to Serializer classes. If you use 2.0, you want to do something like this (in general I think it's the best solution to extend acceptableContentTypes instead of replacing it - you never know what base values it will have in future AFNetworking SDK versions):
AFHTTPRequestOperationManager *requestManager = [AFHTTPRequestOperationManager manager];
NSMutableSet *contentTypes = [requestManager.responseSerializer.acceptableContentTypes mutableCopy];
[contentTypes addObject:#"text/html"];
[contentTypes addObject:#"text/plain"];
// ... etc ...
requestManager.responseSerializer.acceptableContentTypes = [contentTypes copy];
If you decide to use an AFHTTPRequestOperation object directly, just replace "requestManager.responseSerializer" with "myOperationObject.responseSerializer".

Related

NSURL Caching issues

I'm having an issue with a login API. First call works fine, but subsequent calls are cached. This is causing an issue since login/logout functionality is essentially broke.
I've tried many methods and I'm implementing AFNetworking library.
In AppDelegate.m:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
In my Networking class:
(AFHTTPRequestOperation *)createRequestOperationWithMethod:(NSString *) method andPath: (NSString *)path andParams:(NSDictionary *)params
{
GRAPIClient *httpClient = [GRAPIClient sharedClient];
[httpClient setParameterEncoding:AFFormURLParameterEncoding];
NSMutableURLRequest *request = [httpClient requestWithMethod:method
path:path
parameters:params];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData]
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
return operation;
}
I even tried to overwrite the request being generated in AFHTTPClient
In AFHTTPClient.m:
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setTimeoutInterval:2.0];
My GRAPIClient implementation:
#interface GRAPIClient : AFHTTPClient
+ (GRAPIClient *)sharedClient;
+ (BOOL) isInternetReachable;
#end
#implementation GRAPIClient
+ (BOOL) isInternetReachable
{
return reachable;
}
+ (GRAPIClient *)sharedClient {
static GRAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[GRAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kAFAppDotNetAPIBaseURLString]];
});
[_sharedClient setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (status == AFNetworkReachabilityStatusReachableViaWWAN ||
status == AFNetworkReachabilityStatusReachableViaWiFi ) {
NSLog(#"Reachable on!");
reachable = YES;
}
else
{
NSLog(#"Reachable off!");
reachable = NO;
}
}];
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
return self;
}
#end
I've debugged responses from the server and tested with hard coding two NSURLRequests simultaneously to the server. One for User A and one for User B, then printed the response data for both users.
On first login, User A login returned User A credentials. User B returned User B credentials. On second login, User A returned User A credentials, User B returned User A credentials. I have no idea how to fully disable cacheing.
Try:
[operation setCacheResponseBlock:^NSCachedURLResponse*(NSURLConnection* connection, NSCachedURLResponse* cachedResponse) {
return nil;
}];
And:
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
The issue for me as suggested by SixteenOtto was a session being sent from the server and AFNetworking automatically using the cookie. I hadn't considered this before since we're using a restless API based on auth tokens, so a cookie for the session makes no sense. However, inspecting the HTTP response headers with Charles allowed me to see this.
Adding
[request setHTTPShouldHandleCookies:NO];
To my operation generator solved the issue.

How to wrap restkit calls into a service class, and integrate into a controller?

I'm just getting into objective-c, and need some help with this service class I want to write.
I want to create a APIService class, that uses restkit and returns the responses.
It looks like restkit is based on blocks, so when the http call returns, parses the json and returns the resulting collection, I have to somehow make my APIService methods return the response.
I'm looking for help on the skeleton structure of this service, as I am new to objective-c and restkit (which uses blocks).
I'm looking at this example which I want to setup in my own APIService class:
https://github.com/RestKit/RestKit/blob/development/Examples/RKTwitter/Classes/RKTwitterViewController.m#L18
- (void)loadTimeline
{
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager getObjectsAtPath:#"/status/user_timeline/RestKit"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray* statuses = [mappingResult array];
NSLog(#"Loaded statuses: %#", statuses);
_statuses = statuses;
if(self.isViewLoaded)
[_tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
Can someone help me flesh out this skeleton structure, with a method call like the timeline about, here is what I have now:
#interface MyApiService : NSObject
{
#property (nonatomic, strong, readwrite) RKObjectManager *rkObjectManager;
- (id)initWithRKObjectManager:(RKObjectManager *)rkObjectManager;
- (NSArray) loadTimeline:
}
#implementation MyApiService
{
- (id)initWithRKObjectManager:(RKObjectManager *)rkObjectManager
{
self = [super init];
if(self) {
self.rkObjectManager = rkObjectManager;
// ...
}
}
// how to define method for loadTimelines when the call returns using a block?
}
Then I'll use it like this:
// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
MyAPiService *service = [[MyApiService alloc] initWithRKObjectManager: objectManager];
NSArray *statuses = [service loadTimeLine];
But not sure if this is how I will call loadTimeLine since restkit again uses blocks??
RestKit also uses delegates. This is how i do:
Import #import <RestKit/RestKit.h> in your .h file.
Implement the delegate RKObjectLoaderDelegate
Load objects from a service like this.
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager loadObjectsAtResourcePath:#"//status/user_timeline/RestKit" delegate:self];
Implement the delegate methods:
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// Do whatever with objects here...
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
NSLog(#"Error POSTing object : %#", error);
}
There are many useful delegate methods for RKObjectLoaderDelegate.
You can follow a nice RestKit tutorial here.

Passing delegate through another object with ARC

I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys

EXC BAD Access RestKit

In ClassA.h
#interface ClassA : NSObject<RKObjectLoaderDelegate,RKRequestDelegate>
#property(nonatomic,strong)NSMutableDictionary *inputDict;
ClassA.m
//After Implementation
#synthesize inputDict;
-(void)sendRequestWithInputDict:(NSMutableDictionary*)inputDictVal
{
RKURL *baseURL = [RKURL URLWithBaseURLString:baseUrl];
RKObjectManager * manager = [RKObjectManager objectManagerWithBaseURL:baseURL];
[manager setClient:[RKClient sharedClient]];
manager.client.requestQueue.showsNetworkActivityIndicatorWhenBusy = YES;
RKObjectLoader *objectLoader = [manager loaderWithResourcePath:#"/getLocation"];
objectLoader.serializationMIMEType = RKMIMETypeJSON;
objectLoader.method = RKRequestMethodPOST;
objectLoader.params = inputDictVal;
objectLoader.delegate = self;
[objectLoader send];
}
-(void)getLocation
{
inputDict = [[NSMutableDictionary alloc]init];
[self sendRequest:inputDict];
}
baseUrl is declared in constant file which i am importing here.
I am trying to call a sendRequest Function from another class. But i get a EX_BAD_ACCESS in requestWillPrepareForSend(RKRequest.m).
i think some object is released automatically. i don't know which one is...
Check out the instance variables baseUrl and inputDict. Always use properties instead of instance variables and you will never face such problems.
There are many things that are wrong with your code. The most obvious is not retaining the object manager (unless this becomes the sharedManager) another is trying to load an object but using POST. Although, judging by the errors you report, I think your ClassA instance is being dealloced, and because it is set as a delegate you are getting EXC_BAD_ACCESS. I suggest you move to using the block based methods and not the delegate callbacks.
By using Blocks i can able to send request to the server and getting response from it. i found the nice tutorial here http://kalapun.com/blog/2012/05/17/how-i-restkit/
-(void)sendRequest:(NSMutableDictionary*)inputDict withResourcePath:(NSString*)resourcePath
{
RKURL *baseURL = [RKURL URLWithBaseURLString:baseUrl];
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:baseURL];
[manager setClient:[RKClient sharedClient]];
[manager loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *objectLoader){
objectLoader.method = RKRequestMethodPOST;
objectLoader.params = inputDict;
objectLoader.onDidFailWithError = ^(NSError *error){
NSLog(#"Error: %#", [error localizedDescription]);
};
objectLoader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"response: %#", [response bodyAsString]);
};
}];
}

UIWebView iOS5 changing user-agent

How can I change the user-agent of UIWebView in iOS 5?
What I have done so far:
Using the delegate call back, intercept the NSURLRequest, create a new url request and set it's user-agent as whatever I want, then download the data and reload the UIWebView with "loadData:MIMEType:....".
Problem:
This causes infinite recursion, where I load the data, which calls the delegate back, which intern calls the delegate....
Here's the delegate method:
- (BOOL)webView:(UIWebView *)aWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
dispatch_async(kBgQueue, ^{
NSURLResponse *response = nil;
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:[request URL]];
NSDictionary *headers = [NSDictionary dictionaryWithObject:
#"custom_test_agent" forKey:#"User-Agent"];
[newRequest setAllHTTPHeaderFields:headers];
[self setCurrentReqest:newRequest];
NSData *data = [NSURLConnection sendSynchronousRequest:newRequest
returningResponse:&response
error:nil];
dispatch_sync(dispatch_get_main_queue(), ^{
[webView loadData:data
MIMEType:[response MIMEType]
textEncodingName:[response textEncodingName]
baseURL:[request URL]];
});
});
return YES;
}
Change the "UserAgent" default value by running this code once when your app starts:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"Your user agent", #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
EDIT: I have used this with great success, but want to add additional details. To get a user agent, you can enable the "Developer" menu, set the user agent, and then connect to this site to get it printed out for you: WhatsMyAgent. Likewise you can connect using any kind of mobile device, and get it that way too. BTW this is still working just fine in iOS7+
In Swift use this to set UserAgent,
func setUserAgent(){
var userAgent = NSDictionary(objectsAndKeys: "YourUserAgentName","UserAgent")
NSUserDefaults.standardUserDefaults().registerDefaults(userAgent as [NSObject : AnyObject])
}
Use this to test,
println(WebView.stringByEvaluatingJavaScriptFromString("navigator.userAgent"));
When you send message [aWebView loadData:MIMEType:textEncodingName:baseURL:]
then aWebView shouldStartLoadWithRequest: will be called again, and then again - that is why you get an infinite recursion
You should restrict calling of your dispatch_async() block, for example by using some conventional URL:
- (BOOL)webView:(UIWebView *)aWebView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if ([[[request URL] absoluteString] isEqualToString:#"http://yourdomain.com/?local=true"]) {
return YES;
}
...
dispatch_async(...
[aWebView loadData:data
MIMEType:[response MIMEType]
textEncodingName:[response textEncodingName]
baseURL:[NSURL URLWithString:#"http://yourdomain.com/?local=true"]];
);
return NO;
}