AFNetworking 2.0 POST Issue - objective-c

I am in the process of switching over some of my code from AFNetworking 1.0 to 2.0.
Before when doing a POST, I was creating an AFHTTPClient, and an AFHTTPRequestOperation like so:
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:reqUrl];
[httpClient setParameterEncoding:AFJSONParameterEncoding];
httpClient.operationQueue.maxConcurrentOperationCount = 1;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
req.viewName, #"viewName",
req.json, #"JSON",
req.dateAdded.description, #"dateTime",
req.latitude, #"latitude",
req.longitude, #"longitude",
req.heading, #"heading",
req.user, #"requestUser",
nil];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[op setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation,
id responseObject) {
.......convert responseObject (string) to NSDictionary.....
});
This worked fine, and my POSTs went through and I received a successful text response from the server. (which I then converted to a NSDictionary)
I now am using an AFHTTPSessionManager singleton, and calling the POST method from that. When initializing my AFHTTPSessionManager, I am doing the following:
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
[self setResponseSerializer:responseSerializer];
self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"text/html", nil];
Then in my other class, I am calling the POST like so:
NSDictionary *params = #{
#"viewName":req.viewName,
#"JSON":req.json,
#"dateTime":req.dateAdded.description,
#"latitude":req.latitude,
#"longitude":req.longitude,
#"heading":req.heading,
#"requestUser":req.user
};
[netManager POST:path parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
.....
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//failing here
});
My data has not changed at all, but the POSTs always fail with the error:
Error Domain=AFNetworkingErrorDomain Code=-1011 "Request failed: bad request (400)" UserInfo=0x1704675c0 {AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x178234660> { URL: ... } { status code: 400, headers {
"Content-Length" = 2738;
"Content-Type" = "text/html";
Date = "Thu, 15 May 2014 16:13:51 GMT";
Server = "Microsoft-IIS/7.0";
"X-Powered-By" = "ASP.NET";
Whats different that is causing the new AFNetworking 2.0 POST code to not work with this now? Is there anything I need to be setting? The URL and Parameters I am passing are the same as they were with the old way I was sending the POST.
Thanks

My solution ended up being a pretty simple one
In my AFHTTPSessionManager's init, I was not setting the RequestSerializer along with the ResponseSerializer.
After setting it correctly, my POSTs are going through fine again. Heres what I set:
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", nil];
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
EDIT
Aaron Brager stated that those first 2 lines are defaults and not needed. All I needed was to set the RequestSerializer. I tested and can verify this.

Related

RESTKit DELETE request not deleting local object on 2xx success

According to the docs for 0.20 RK:
RKManagedObjectRequestOperation adds special behavior to DELETE requests. Upon retrieving a successful (2xx status code) response for a DELETE, the operation will invoke deleteObject: with the operations targetObject on the managed object context. This will delete the target object from the local store in conjunction the successfully deleted remote representation.
I have been trying to delete an object with such a request but no matter what I try I can't seem to get it to work. I successfully perform a request for many objects which get mapped to appropriate class, and get stored in core data. When I attempt a delete request on one of the objects and get a 200 success back, it does not deleted from local store.
Here's some code where I am no doubt missing a trick.
AppDelegate.m
...
//
// Match Mapping
//
RKEntityMapping *matchMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Match class])
inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *matchAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
#"objectId", #"id",
#"score", #"matchScore",
#"date", #"matchDate",
nil];
matchMapping.identificationAttributes = #[#"objectId"];
[matchMapping addAttributeMappingsFromDictionary:matchAttributes];
// Response descriptor for GET
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodGET
pathPattern:#"match/"
keyPath:#"matches"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Response Descriptor for PUT
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodPUT
pathPattern:#"match/"
keyPath:#"match"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Request Descriptor for DELETE
[objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:[matchMapping inverseMapping]
objectClass:[Match class]
rootKeyPath:nil
method:RKRequestMethodDELETE]];
MatchDetailVC.m
...
- (void)deleteMatch {
NSDictionary *requiredParameters = #{
#"APIKey": #"xxxxx"
};
NSMutableURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:self.match
method:RKRequestMethodDELETE
path:#"match/"
parameters:requiredParameters];
RKManagedObjectRequestOperation *operation = [[RKObjectManager sharedManager]
managedObjectRequestOperationWithRequest:request
managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext save:nil]; // IS THIS NEEDED?
NSLog(#"Successfully deleted match.");
[self.navigationController popToRootViewControllerAnimated:YES];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
}
...
Thanks in advance and if you need more code, let me know.
Andy
I know this is quite an old post, but here is what I found out after searching for ages...
The local delete will fail if there is no valid response mapping for the DELETE response.
The problem wen't away for me when I created an empty response mapping like this:
RKObjectMapping* nullMapping = [RKObjectMapping mappingForClass:[NSNull class]];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:nullMapping method:RKRequestMethodDELETE pathPattern:#"mybase/something/:myid/" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];

Dealing with AFNetworking2.0 asynchronous HTTP request

I am very new to the concept of asynchronous programming, but I get the general gist of it (things get run in the backround).
The issue I'm having is I have a method which utilizes AFNetworking 2.0 to make an HTTP post request, and it works for the most part.
However, I can't figure out how to get the method to actually return the value received from the response as the method returns and THEN gets the value from the response.
-(int) registerUser
{
self.responseValue = 000; //Notice I set this to 000 at start of method
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSError *err = nil;
self.responseValue = [[responseObject objectForKey:#"res"] intValue];
//Note: This^ value returns 99 and NSLogs confirm this
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
}];
return self.responseValue; //This will return 000 and never 99!
}
Whats the 'proper' way to handle this situation? I've heard whispers of using a 'callback', but I don't really understand how to implement that in this situation.
Any guidance or help would be awesome, cheers!
The issue is that the POST runs asynchronously, as you point out, so you are hitting the return line well before the responseValue property is actually set, because that success block runs later. Put breakpoints/NSLog statements in there, and you'll see you're hitting the return line first.
You generally do not return values from an asynchronous methods, but rather you adopt the completion block pattern. For example:
- (void)registerUserWithCompletion:(void (^)(int responseValue, NSError *error))completion
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
int responseValue = [[responseObject objectForKey:#"res"] intValue];
if (completion)
completion(responseValue, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
if (completion)
completion(-1, err); // I don't know what you want to return if it failed, but handle it appropriately
}];
}
And then, you could use it as follows:
[self registerUserWithCompletion:^(int responseValue, NSError *error) {
if (error)
NSLog(#"%s: registerUserWithCompletion error: %#", __FUNCTION__, error);
else
NSLog(#"%d", responseValue);
// do whatever you want with that responseValue here, inside the block
}];
// Needless to say, don't try to use the `responseValue` here, because
// `registerUserWithCompletion` runs asynchronously, and you will probably
// hit this line of code well before the above block is executed. Anything
// that is dependent upon the registration must called from within the above
// completion block, not here after the block.
Note, I'd suggest you retire that responseValue property you had before, because now that you're using completion blocks, you get it passed back to you via that mechanism, rather than relying on class properties.
Check this one and use search ;-))
Getting variable from the inside of block
its a lot of duplicates already!
:-)

Kiwi - Expected subject to be nil, got KWAsyncVerifier

I'm using Kiwi to write the tests for my app.
I wrote tests to test against my API. I was guided by this example in the documentation for testing asynchronous calls:
https://github.com/allending/Kiwi/wiki/Asynchronous-Testing
My tests are long, so I made a simplified version of my issue:
describe(#"My Class Name", ^{
context(#"populate", ^{
it(#"download the content", ^{
__block NSString *testResponseObject = nil;
__block NSError *testError = nil;
MyClient *apiClient = [MyClient sharedClient];
NSMutableURLRequest *request = [apiClient requestWithMethod:#"DELETE" path:#"my/path" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
testError = error;
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
[[expectFutureValue(testError) shouldEventuallyBeforeTimingOutAfter(100)] shouldBeNil];
});
});
});
The thing is that if everything works as expected & the operation succeeds the failure block never gets called & instead of nil for NSError I get KWAsyncVerifier.
I'm guessing that's because Kiwi waits for the block where testError is referenced to be executed which never happens & that's why I have KWAsyncVerifier stuck into testError instead of nil.
Is there any alternative how to test this out?
My first recommendation is that you should not test your libraries. From what I read in your example, you are basically checking that AFHTTPRequestOperation is working as documented, but that’s not your responsability to test. You should test that you invoke AFNetworking correctly, and that given an responseObject or an error, your code behaves as you expect.
Anyway, about what you are seeing, you have two “shoulds” in the same line: shouldEventually and shouldBeNil; they use to have beNil matcher, which is unavailable in 2.1, and I think they are bringing back. You can find the discussion in https://github.com/allending/Kiwi/issues/293
Maybe you can try the following to make sure that the failure branch is not taken:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// This will fail if we ever reach this branch
[error shouldBeNil];
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
The shouldEventuallyBeforeTimingOutAfter will keep the test case “alive” waiting to check the response, but if you ever go through the failure branch, the other expectation will fail (and also the one in response will fail after 100 seconds). Hope it helps.

AFNetworking and submitting html form with user validated captcha

I've trying to be able to login in a website which has an html form with a captcha. The way I've been trying to do it is fetching the html of the login form, showing the captcha to a user, who will put it in a textfield and then I'm trying to submit the form.
The error I'm always getting is invalid key code, so I'm guessing the problem is the captcha I fetch in the first instance, isn't valid for the second one... Any ideas how I could do this?
The webpage is Fanfiction, and I'm doing this as a personal proyect and see if I'm capable of exporting my list of favorites and follows.
I do this in order to show the captcha to the user.
NSURL *url = [NSURL URLWithString:#"https://www.fanfiction.net"];
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[self.httpClient getPath:#"/login.php" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
TFHpple * doc = [[TFHpple alloc] initWithHTMLData:responseObject];
NSArray * elements = [doc searchWithXPathQuery:#"//img[#id='xcaptcha']"];
TFHppleElement * element = [elements objectAtIndex:0];
[self.captchaView setImageWithURL:[NSURL URLWithString:[element objectForKey:#"src"]]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
And then, whenthe user has entered the captcha code in a textfield and pressed a UIButton, I do this
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
kFFNMail, #"email",
kFFNPass, #"password",
self.captchaField.text, #"captcha",
nil];
NSURLRequest *postRequest = [self.httpClient multipartFormRequestWithMethod:#"POST"
path:#"/login.php"
parameters:params
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { }];
/*
// I think this is the same as the one before in this case
NSMutableURLRequest *postRequest = [self.httpClient requestWithMethod:#"POST"
path:#"/login.php"
parameters:params2];
*/
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:postRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
TFHpple * doc = [[TFHpple alloc] initWithHTMLData:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
[operation start];
If is of any indication, I have this working in a ruby script like this
require 'rubygems'
require 'mechanize'
require "highline/import"
a = Mechanize.new
a.get('https://www.fanfiction.net/login.php') do |page|
images = page.search("#xcaptcha")
a.get(images.first.attributes["src"]).save "captcha.jpg"
# I read the saved image,and enter the captcha code
captcha = ask "Input captcha: "
# Submit the login form
my_page = page.form_with(:action => '/login.php') do |f|
f.email = my_mail
f.password = my_pass
f.captcha = captcha
end.click_button
# already logged!
a.get('https://www.fanfiction.net/alert/story.php') do |page|
page.links.each do |link|
text = link.text.strip
next unless text.length > 0
puts text
end
end
end
well, turns out I was doing everything ok, just that I had forgotten one extra parameter in the form, a hidden input which had the id of the captcha.
I just needed to capture the id at the same time that I captured the captcha image, and then send it in the form POST as an extra parameter.
Hope this helps anyone.

How to use Restkit to POST JSON and map response

I'm using RestKit for the first time, and its feature-set looks great. I've read the document multiple times now and I'm struggling to find a way to POST JSON params to a feed and map the JSON response. From searching on stackoverflow I found a way to send the JSON params via a GET, but my server only takes POST.
Here is the code I have so far:
RKObjectMapping *issueMapping = [RKObjectMapping mappingForClass:[CDIssue class]];
[objectMapping mapKeyPath:#"issue_id" toAttribute:#"issueId"];
[objectMapping mapKeyPath:#"title" toAttribute:#"issueTitle"];
[objectMapping mapKeyPath:#"description" toAttribute:#"issueDescription"];
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://restkit.org"];
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"News.sqlite"];
objectManager.objectStore = objectStore;
NSDictionary params = [NSDictionary dictionaryWithObjectsAndKeys: #"myUsername", #"username", #"myPassword", #"password", nil];
NSURL *someURL = [objectManager.client URLForResourcePath:#"/feed/getIssues.json" queryParams:params];
[manager loadObjectsAtResourcePath:[someURL absoluteString] objectMapping:objectMapping delegate:self]
From the another stackoverflow thread (http://stackoverflow.com/questions/9102262/do-a-simple-json-post-using-restkit), I know how to do a simple POST request with the following code:
RKClient *myClient = [RKClient sharedClient];
NSMutableDictionary *rpcData = [[NSMutableDictionary alloc] init ];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
//User and password params
[params setObject:password forKey:#"password"];
[params setObject:username forKey:#"email"];
//The server ask me for this format, so I set it here:
[rpcData setObject:#"2.0" forKey:#"jsonrpc"];
[rpcData setObject:#"authenticate" forKey:#"method"];
[rpcData setObject:#"" forKey:#"id"];
[rpcData setObject:params forKey:#"params"];
//Parsing rpcData to JSON!
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:RKMIMETypeJSON];
NSError *error = nil;
NSString *json = [parser stringFromObject:rpcData error:&error];
//If no error we send the post, voila!
if (!error){
[[myClient post:#"/" params:[RKRequestSerialization serializationWithData:[json dataUsingEncoding:NSUTF8StringEncoding] MIMEType:RKMIMETypeJSON] delegate:self] send];
}
I was hoping someone would help me marry these two code snippets into a workable solution.
To post an object what I do is associate a path to an object. Then use the method postObject from RKObjectManager.
I asume that you have already configured RestKit so you have the base path set and defined the object mapping for your CDIssue as you have in the code that you already have. With that in mind try this code:
//We tell RestKit to asociate a path with our CDIssue class
RKObjectRouter *router = [[RKObjectRouter alloc] init];
[router routeClass:[CDIssue class] toResourcePath:#"/path/to/my/cdissue/" forMethod:RKRequestMethodPOST];
[RKObjectManager sharedManager].router = router;
//We get the mapping for the object that you want, in this case CDIssue assuming you already set that in another place
RKObjectMapping *mapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[CDIssue class]];
//Post the object using the ObjectMapping with blocks
[[RKObjectManager sharedManager] postObject:myEntity usingBlock:^(RKObjectLoader *loader) {
loader.objectMapping = mapping;
loader.delegate = self;
loader.onDidLoadObject = ^(id object) {
NSLog(#"Got the object mapped");
//Be Happy and do some stuff here
};
loader.onDidFailWithError = ^(NSError * error){
NSLog(#"Error on request");
};
loader.onDidFailLoadWithError = ^(NSError * error){
NSLog(#"Error on load");
};
loader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"Response did arrive");
if([response statusCode]>299){
//This is useful when you get an error. You can check what did the server returned
id parsedResponse = [KFHelper JSONObjectWithData:[response body]];
NSLog(#"%#",parsedResponse);
}
};
}];