It won't let me attached the params to the request, what am I doing wrong? Params is a Dictionary and endString adds to the sharedClient baseURL.
[[RKClient sharedClient] get:endString usingBlock:^(RKRequest *loader){
loader.params = [RKParams paramsWithDictionary:params];
loader.onDidLoadResponse = ^(RKResponse *response) {
[self parseJSONDictFromResponse:response];
};
loader.onDidFailLoadWithError = ^(NSError *error) {
NSLog(#"error2:%#",error);
};
}];
I get this error:RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?
I think you are on the right track. Below is from a working example I found here, about 2/3 the way down the page. Another option for you may be to append the params directly to the URL. I'm not sure if that's feasible for you, but if your parameters are simple then it may be.
- (void)authenticateWithLogin:(NSString *)login password:(NSString *)password onLoad:(RKRequestDidLoadResponseBlock)loadBlock onFail:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
[[RKClient sharedClient] post:#"/login" usingBlock:^(RKRequest *request) {
request.params = [NSDictionary dictionaryWithKeysAndObjects:
#"employee[email]", login,
#"employee[password]", password,
nil];
request.onDidLoadResponse = ^(RKResponse *response) {
id parsedResponse = [response parsedBody:NULL];
NSString *token = [parsedResponse valueForKey:#"authentication_token"];
//NSLog(#"response: [%#] %#", [parsedResponse class], parsedResponse);
if (token.length > 0) {
NSLog(#"response status: %d, token: %#", response.statusCode, token);
[[RKClient sharedClient] setValue:token forHTTPHeaderField:#"X-Rabatme-Auth-Token"];
if (loadBlock) loadBlock(response);
}
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
request.onDidFailLoadWithError = failBlock;
}];
}
You should also take a look at this SO question: RestKit GET query parameters.
Related
I've just started using the new AFNetworking 2.0 API having used the previous versions for a while now. I'm trying to do a bog standard http POST request, but sadly I'm not doing too well. This is my current code:
AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"username" : self.usernameField.text,
#"password" : self.passwordField.text};
[operationManager POST:#"https:URL GOES HERE" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", [responseObject description]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Now this returns a JSON of (NULL) and doesn't give me a status code like 404 or something (incidentally how do we attain the status code when using AFN 2.0?). However, when I try the information with a web app like apikitchen.com which tests the HTTP Post request for me, it works when I put the username and password in the param field. So really my question is, why don't the parameters in the AFN 2.0 parameter property act in the same way as the parameters in the web app? And more generally why aren't the post request parameters working for me in AFN 2.0?
Thanks for the help in advance,
Mike
EDIT: I'm struggling with the implementation of the suggested fix. My Post method now looks like this, but It doesn't make sense to me right now.
AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"username" : self.usernameField.text,
#"password" : self.passwordField.text};
operationManager.requestSerializer.queryStringSerializationWithBlock =
^NSString*(NSURLRequest *request,
NSDictionary *parameters,
NSError *__autoreleasing *error) {
NSString* encodedParams = form_urlencode_HTTP5_Parameters(parameters);
return encodedParams;
};
[operationManager POST:#"URL HERE" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", [responseObject description]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Now this returns a JSON of (NULL) and doesn't give me a status code like 404 or something (incidentally how do we attain the status code when using AFN 2.0?).
It should at least give an error. What does the failure handler print out?
So really my question is, why don't the parameters in the AFN 2.0 parameter property act in the same way as the parameters in the web app? And more generally why aren't the post request parameters working for me in AFN 2.0?
After examining how AFN (Version 2.0.1) encodes the parameters, it appears to me that these aren't encoded as they should: The application/x-www-form-urlencoded encoding algorithm.
Until this has been fixed, you may try the following workaround. The following algorithm encodes parameters strictly as suggested by w3c for HTTP 5, at least for Mac OS X 10.8 where I've tested it:
static NSString* form_urlencode_HTTP5_String(NSString* s) {
CFStringRef charactersToLeaveUnescaped = CFSTR(" ");
CFStringRef legalURLCharactersToBeEscaped = CFSTR("!$&'()+,/:;=?#~");
NSString *result = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)s,
charactersToLeaveUnescaped,
legalURLCharactersToBeEscaped,
kCFStringEncodingUTF8));
return [result stringByReplacingOccurrencesOfString:#" " withString:#"+"];
}
(Note: the code above depends on the implementation details of function CFURLCreateStringByAddingPercentEscapes. It's entirely possible to implement the suggested algorithm easily without any dependencies, which I would recommend - it becomes just not that short.)
static NSString* form_urlencode_HTTP5_Parameters(NSDictionary* parameters)
{
NSMutableString* result = [[NSMutableString alloc] init];
BOOL isFirst = YES;
for (NSString* name in parameters) {
if (!isFirst) {
[result appendString:#"&"];
}
isFirst = NO;
assert([name isKindOfClass:[NSString class]]);
NSString* value = parameters[name];
assert([value isKindOfClass:[NSString class]]);
NSString* encodedName = form_urlencode_HTTP5_String(name);
NSString* encodedValue = form_urlencode_HTTP5_String(value);
[result appendString:encodedName];
[result appendString:#"="];
[result appendString:encodedValue];
}
return [result copy];
}
Then, when using AFN, you can customize the serializing algorithm as shown below:
AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];
operationManager.requestSerializer.queryStringSerializationWithBlock =
^NSString*(NSURLRequest *request,
NSDictionary *parameters,
NSError *__autoreleasing *error) {
NSString* encodedParams = form_urlencode_HTTP5_Parameters(parameters);
return encodedParams;
};
Put / after the url. I've missed it for hours.
I am building an iphone app with a rails-backed server. I am using the devise gem. I am having trouble with user logins on the client-side (everything works on the web side, and even in the terminal with CURL).
On Xcode I can create a user and I can login. After logging in
(and recieving this in the log: "User logged in!")
I am then pushed to the indexViewController- and here I receive an error that the posts don't load. The reason is because on the post_controller.rb I have a
before_filter :authenticate_user!
preventing the posts from loading. The problem is, that the auth_token which was generated upon a successful login, is not being stored and passed along to the different views. So then, in the log I get:
'You need to sign in before continuing.'
As if the first part of what I just explained never happened..
In the indexViewController viewDidLoad method I have:
if (![[APIClient sharedClient] isAuthorized]) {
LoginViewController *loginViewController = [[LoginViewController alloc] init];
[self.navigationController pushViewController:loginViewController animated:YES];
}
isAuthorized is a BOOL in the APIClient that checks if userID>0
In the user model this is the code that creates a login session
+ (void)loginUser:(NSString *)signature
email:(NSString *)email
password:(NSString *)password
block:(void (^)(User *user))block
{
NSDictionary *parameters = #{ #"user": #{
// #"signature": signature,
#"email": email,
#"password": password
}
};
[[APIClient sharedClient] postPath:#"/users/sign_in" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [[User alloc] initWithDictionary:responseObject];
if (block) {
block(user);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
}
I am guessing it is here that I am missing some auth_token implementation? Since it is generated automatically by devise- I am not sure how to tell xcode to remember it. The auth_token is a string that has a column in the user table on the db. Should I add auth_token as param to the dictionary that holds the user's email and username? Or how do I get the token to persist?
Any ideas would be helpful.
Not being intimately familiar with AFNetworking this is a stab in the dark, but presumably you need to set the token for subsequent requests. Assuming APIClient is a wrapper you've added around AFHTTPClient, here's a quick idea of what that might look like after reviewing the code here - AFHTTPClient.
+ (void)loginUser:(NSString *)signature
email:(NSString *)email
password:(NSString *)password
block:(void (^)(User *user))block {
NSDictionary *parameters = #{ #"user": #{ #"email": email,
#"password": password } };
[[APIClient sharedClient] postPath:#"/users/sign_in"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [[User alloc] initWithDictionary:responseObject];
// retrieve and save auth token
NSString *token = [responseObject objectForKey:#"authToken"];
[[APIClient sharedClient] setAuthorizationHeaderWithToken:token];
if (block) {
block(user);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
}
iam using restKit, to send and receive data from server... iam getting back
{
"request":"globalUpdate",
"updateRevision":2,
"updatedObjects":{
"users":[
{
id:"someid1",
name:"somename"
},
{
id:"someid2",
name:"somename2",
}
]
}
}
i want to use
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:nil usingBlock:^(RKObjectLoader * loader){)];
to load only objects inside updatedObjects into CoreData and request, updateRevision into NSDictionary
so in
loader.onDidLoadObjects = ^(NSArray *objects) {
}
the first object is the Dictionary and the later one are CoreData
Well this is the matter of your choice which object you use for the coredata and which for your own purpose. RKObjectLoader also provides onDidLoadResponse block which has a reference to the response which you could use for your own use.
[myobjectManager loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.mappingProvider = [RKObjectMappingProvider mappingProviderUsingBlock:^(RKObjectMappingProvider *provider) {
[provider setObjectMapping:[MyClass mapping] forKeyPath:#"updatedObjects"];
loader.onDidLoadObjects = ^(NSArray *objects){
};
loader.onDidLoadResponse = ^(RKResponse *response){
// NSData * data = [response data];
id object = [response parsedBody:nil];
// now parse the data yourself which will give you
// the entire json in NSData for and parse it,
// extract the component you need
};
loader.onDidFailWithError = ^(NSError *error){
};
}];
}];
Having an issue which is more of a design consideration than that of code.
My iOS app interfaces with a json web service. I am using AFNetworking and my issue is basically I need the init function (which authenticates the AFHTTPClient and retrieves a token) to complete entirely before I make any additional requests (that require said token).
From the code below, I would be interested in hearing design approaches to achieving this, I would prefer to keep all requests async an alternative solution would be to make the request in initWithHost:port:user:pass synchronous (not using AFNetworking) which I am aware is bad practice and want to avoid.
DCWebServiceManager.h
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"
#interface DCWebServiceManager : NSObject
{
NSString *hostServer;
NSString *hostPort;
NSString *hostUser;
NSString *hostPass;
NSString *hostToken;
AFHTTPClient *httpClient;
}
// Designated Initialiser
- (id)initWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass;
// Instance Methods
- (void)getFileList;
#end
DCWebServiceManager.m
#import "DCWebServiceManager.h"
#import "AFHTTPClient.h"
#import "AFHTTPRequestOperation.h"
#import "AFJSONRequestOperation.h"
#implementation DCWebServiceManager
- (id)initWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass
{
self = [super init];
if (self)
{
hostServer = host;
hostPort = port;
hostUser = user;
hostPass = pass;
NSString *apiPath = [NSString stringWithFormat:#"http://%#:%#/", hostServer, hostPort];
httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:apiPath]];
[httpClient setAuthorizationHeaderWithUsername:hostUser password:hostPass];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"authenticate.php" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
// Do operations to parse request token to be used in
// all requests going forward...
// ...
// ...
// Results in setting: hostToken = '<PARSED_TOKEN>'
NSLog(#"HostToken: >>%#<<", hostToken);
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"ERROR: %#", operation.responseString);
}];
[operation start];
}
return self;
}
- (void)getFileList
{
// *************************
// The issue is here, getFileList gets called before the hostToken is retrieved..
// Make the authenticate request in initWithHost:port:user:pass a synchronous request perhaps??
// *************************
NSLog(#"IN GETFILELIST: %#", hostToken); // Results in host token being nil!!!
NSString *queryString = [NSString stringWithFormat:#"?list&token=%s", hostToken];
NSMutableURLRequest *listRequest = [httpClient requestWithMethod:#"GET" path:queryString parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:listRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON){
NSLog(#"SUCCESS!");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"ERROR!: %#", error);
}];
[operation start];
}
#end
ViewController.m
....
DCWebServiceManager *manager = [[DCWebServiceManager alloc] initWithHost:#"localhost" port:#"23312" user:#"FOO" pass:#"BAR"];
[manager getFileList];
// OUTPUTS
IN GETFILELIST: (nil)
HostToken: >>sdf5fdsfs46a6cawca6<<
....
...
I'd suggest subclassing AFHTTPClient and adding a +sharedInstance and property for the token.
+ (MyGClient *)sharedInstanceWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass {
static MyClient *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[... your code from the init ...]
});
return sharedInstance;
}
You can then override enqueueHTTPRequestOperationWithRequest:success:failure to check for the token before enqueueing further operations.
Additionally, you can collect the operations and enqueue them as soon as the token is set by overriding the setter for the property.
Like #patric.schenke said, you can subclass AFHTTPClient if you want to clean up some of your code, but the real issue is that you need to make another request to authenticate (if your token is nil) before making the request to getFileList.
I would recommend using blocks in the same way that AFNetworking is using blocks to remain asynchronous. Move your HTTP call into its own method and call it only when your hostToken is nil:
- (void)getFileList
{
if (self.token == nil) {
[self updateTokenThenWhenComplete:^(void){
// make HTTP call to get file list
}];
} else {
// make HTTP call to get file list
}
}
- (void)updateTokenThenWhenComplete:(void (^))callback
{
//... make HTTP request
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
self.token = responseObject.token;
callback();
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
//...
}];
}
In my iOS app, I just want to check if the user has granted the Facebook publish_stream permission.
I'm not sure how to handle the response to the call
[facebook requestWithGraphPath:#"me/permissions" andDelegate:self];
in my FBRequest delegate method. I've tried:
if (request == self.permissionRequest) {
NSString *key = [result objectForKey:#"publish_stream"];
DLog(#"Key: %#", key);
}
But I get null.
And if I try
id *key = [result objectForKey:#"publish_stream"];
int keyInt = [key integerValue];
DLog(#"Key: %i", keyInt);
I always get 0. Even when I know the permission is active...
You can make an FBRequest with the open graph API with "me/permissions".
It will return you a response with a dictionary where the key are the permissions. a value will be associated (1 = YES, 0 = NO)
Here's a working sample I did for create_events. Sure it could be used for others:
FBRequest *eventPostOK = [FBRequest requestWithGraphPath:#"me/permissions" parameters:Nil HTTPMethod:#"GET"];
[eventPostOK startWithCompletionHandler: ^(FBRequestConnection *connection,
NSDictionary* result,
NSError *error) {
BOOL canDoIt = FALSE;
if (!error)
{
FBGraphObject *data = [result objectForKey:#"data"];
for(NSDictionary<FBGraphObject> *aKey in data) {
canDoIt = [[aKey objectForKey:#"create_event"] boolValue];
}
}
else
NSLog(#"%#", error);
NSLog(#"%#", canDoIt ? #"I can create Events" : #"I can't create Events");
}];