I attempting to query a hosted instance of elasticsearch (Searchbox.io) using AFNetworking. Unfortunately I can't seem to get the syntax working properly. For example, the following statement using curl works just fine and returns exactly what I would expect.
curl -XGET 'http://api.searchbox.io/api-key/<MYAPIKEY>/<MYINDEX/_search' -d '{"query" : {"query_string" : {"query" : "searchTerm"}}}'
I get back the docs from Searchbox.io that contain "searchTerm". Super. However, when I try to implement the same in AFNetworking, the Searchbox.io API returns a 500 error, noting "Parse Failure [Failed to parse source [na]]]; nested" Below is the call in my subclass of the AFHTTPClient.
NSDictionary *searchDict = #{#"query":#{#"query_string":#{#"query":#"searchTerm"}}};
[self getPath:[NSString stringWithFormat:#"_search"]
parameters:searchDict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Search Results: \n\n %#", responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Search Error: %#", error);
}
}];
FYI - the subclassed AFHTTPClient is set with the following statements prior to this call. The subclassed AFHTTPClient client is working properly and the baseURL is set correctly.
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
To my newbie eyes, it looks like AFNetworking is handling the searchDict differently than the literal in the curl statement. To summarize, what would be the equivalent of the curl statement above, as implemented in AFNetworking? Thanks in advance
After digging around a bit more, I was able to get it to work by:
Pulling the latest rev of AFNetworking (1.3.1)
Changing the initWithBaseURL method of my AFHTTPClient subclass to the code below
Importing "AFJSONRequestOperation.h" into my AFHTTPClient subclass
Using the postPath:parameters:success:failure method to hit the API
[self setParameterEncoding:AFJSONParameterEncoding];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
It is beyond me as to why the curl statement would be -XGET and the AFNetworking method would be a POST. Surely my misunderstanding.
Related
My code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] ;
manager.requestSerializer = [AFJSONRequestSerializer serializerWithWritingOptions:NSJSONReadingAllowFragments];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:[_urlBase stringByAppendingPathComponent:_urlRequest]
parameters:paramDictionary
success:^(NSURLSessionDataTask *task, id responseObject){
dispatch_async(dispatch_get_main_queue(),^{
[self AFRequestFinished:responseObject];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error){
NSLog(#"JSON ERROR PARAMETERS: %#", error);
}
];
I am using this POST request to send several types of data up to a server along with pictures. I am using something very similar for the GET request and it works fine. Whenever I run this code I get a EXC_BAD_ACCESS CODE=1 error on the following line of AFNetworking 2.0. The responseObject is 0x0:
responseObject = [self.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
The above line of code is within the if/else method in:
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
UPDATE
I ran instruments on the code, and it there is a zombie present. AFNetworking is trying to make a call to the NSError, but it has been deallocated. I believe this has arisen because the POST call initially succeeds, but there is still an error that is flagged. So it initially thinks there is no error and sets it to nil, but then tries to call for it in the error block of the POST.
If you're using the most recent version, you may be experiencing this known issue when the JSON serializer returns an error. You can work around this until a new release is made by:
removing the #autoreleasepool in the serializer, or
changing the scope of the error to outside the autorelease pool
(Both solutions are outlined in the issue linked above.)
On a side note, there's no need to dispatch to the main queue in the completion handler. AFNetworking guarantees that completion blocks are called on the main thread.
I could not figure out how to change the value of results inside the success block. I use __block like some post suggests but results is forever nil. I set breakpoint inside of block and make sure that JSON is not nil, which download data as I expected.
I am using AFNetworking library if that's relevant.
+(NSArray *)eventsByCityID:(NSString *)cityID startIndex:(NSUInteger)start count:(NSUInteger)count
{
__block NSArray *results = nil;
[[DoubanHTTPClient sharedClient] getPath:#"event/list" parameters:#{#"loc":dataSingleton.cityID} success:^(AFHTTPRequestOperation *operation, id JSON) {
results = [JSON valueForKey:#"events"];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"download events error: %# \n\n",error);
}];
return results;
}
More likely than not, that [very poorly named] method getPath:parameters:success:failure: is asynchronous.
Thus, you need to tell something in the success block that the value has changed. I.e.
^{
[something yoManGotEvents:[JSON valueForKey:#"events"]];
}
(Methods shouldn't be prefixed with get outside of very special circumstances. Third party libraries with lots of API using that prefix outside of said circumstances raise question as to what other system specific patterns they may not be following.)
I am trying to send a POST request to a server using AFNetworking, and everything seems to be working, i.e. the application is successfully pinging the server. However, the parameter values that it is sending are blank when it reaches the server even though after stepping through my code below using the debugger, the values appear to be being passed successfully. Any help on this would be greatly appreciated.
APIClient.m
#import "APIClient.h"
#import "AFJSONRequestOperation.h"
// Removed URL for privacy purposes.
static NSString * const kAPIBaseURLString = #"string goes here";
#implementation APIClient
+ (APIClient *)sharedClient {
static APIClient *_sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:kAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[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
Login Method in LoginBrain.m
- (void)loginUsingEmail:(NSString *)email andPassword:(NSString *)password withBlock:(void (^)(NSDictionary *loginResults))block {
self.email = email;
self.password = password;
// Removed path for privacy purposes
[[APIClient sharedClient] postPath:#"insert path here" parameters:[NSDictionary dictionaryWithObjectsAndKeys:email, #"uname", password, #"pw", nil] success:^(AFHTTPRequestOperation *operation, id responseJSON) {
if (block) {
block(responseJSON);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
// Store user data in app?
}
Login Called Method in LoginViewController.m
- (IBAction)loginPressed {
[self.loginProgressIndicator startAnimating];
NSString *email = self.emailTextField.text;
NSString *password = self.passwordTextField.text;
[self.brain loginUsingEmail:email andPassword:password withBlock:^(NSDictionary *loginResults) {
[self.loginProgressIndicator stopAnimating];
[self.delegate uloopLoginViewController:self didLoginUserWithEmail:email andPassword:password];
}];
}
UPDATE
I tried changing the parameterEncoding as recommended here, but it did not fix the problem.
SECOND UPDATE
Here is the PHP code from the server side that is accessing the POST data. This was written by a co-worker of mine, as I don't do anything on the server side and am very unfamiliar with how it works.
header('Content-type: application/json');
$username = $_POST['uname'];
$pw = $_POST['pw'];
The server code is pretty straight forward. He has some sort of log script that checks to see what the variable values are, and he says that the client is hitting the server, but the variable values are blank.
THIRD UPDATE
This is a dump of the HTTP request by generating a print_r of the $_REQUEST variable:
Array ( [sid] => FwAqvZrfckw )
And here is a dump of the $_POST variable. As you can see, it's completely blank:
Array ( )
FOURTH UPDATE
I used Wireshark to capture the packet before it's being sent to the server, and everything appears to be in order:
Accept: application/json
Content-Type: application/x-www-form-urlencoded; charset=utf-8
And the POST parameters were all there as well. We also created a test file on the server side and just did a test POST to make sure that the code there is working, and it is.
Thank you.
With the same problem, using AFFormURLParameterEncoding was what I needed.
So just to simplify all the thread, you have to use :
[[APIClient sharedClient] setParameterEncoding:AFFormURLParameterEncoding];
I don't see anything in particular that would cause a problem here but I'll start off by giving you the steps I used to solve a similar problem.
To start, checkout the tool, Charles, which is a Debugging Web Proxy that will intercept the response from the server and should give you a more clear idea of what's going wrong. There's a 30 day free trial and it really helped me pick out the little bugs. To use it, press the sequence button and filter the results via your server url. From there you can see the request and response sent and received from the server. If the following doesn't fix your problem, post the request and response that Charles spits out.
Fix wise, try adding [[APIClient sharedClient] setParameterEncoding:AFJSONParameterEncoding] right before you send the POST request. It looks like yall are using JSON as the server-side format.
So in loginUsingEmail:
self.email = email;
self.password = password;
[[APIClient sharedClient] setParameterEncoding:AFJSONParameterEncoding];
[[APIClient sharedClient] postPath:#"insert path here" parameters:[NSDictionary dictionaryWithObjectsAndKeys:email, #"uname", password, #"pw", nil] success:^(AFHTTPRequestOperation *operation, id responseJSON) {
if (block) {
block(responseJSON);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
// Store user data in app?
}
I am creating an app where in the first view, the user is given the option to log-in or register. In the register view is a UITableViewCell that, when clicked, takes the user to a view containing a UITableView and a UIPickerView. The UITableView is working correctly, but the UIPickerView, which is supposed to dynamically pull the data it is supposed to display using a web call, is showing up but appears completely blank. Putting in a few NSLog statements, I noticed that the methods in the Model that pull the data using AFNetworking are never getting called. I've posted the code below for the UIPickerViewDelegate and UIPickerViewDataSource methods, as well as the method that is supposed to pull the data in the Model. Thanks in advance.
UIPickerViewDelegate
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return [[self.brain classChoicesForSignUp] objectAtIndex:row];
}
UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
size_t numberOfRows = [self.brain classChoicesForSignUp].count;
NSLog(#"Number of Rows: %#", [[NSNumber numberWithFloat:numberOfRows] stringValue]);
return numberOfRows;
}
SignUpPickerBrain.m
#import "SignUpPickerBrain.h"
#import "AFJSONRequestOperation.h"
#implementation SignUpPickerBrain
#pragma mark - Picker Data
- (NSArray *)classChoicesForSignUp {
NSLog(#"Class choices method called");
// Note that in my code, the actual URL is present here.
NSURL *url = [NSURL URLWithString:#"the URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Success!");
NSLog([JSON description]);
} failure:nil];
[operation start];
[operation waitUntilFinished];
NSLog([operation responseJSON]);
return [operation responseJSON];
}
#end
There are a lot of anti-patterns going on in this code sample. I strongly recommend against your current approach, and consider the following points:
Do networking asynchronously, i.e. don't use [operation waitUntilFinished];. Any time you're creating a method that makes a network request, give it a block parameter that can be used as a callback once the results come in.
Store your results in an array property in the controller, or the like, and use that to drive your delegates and datasources. In your current approach, you will be doing a network request every single time a row is displayed (!). So instead, initialize to an empty array, and once the new results are set to that property, reload the data source. One asynchronous request. Easy.
Get rid of SignUpPickerBrain. Either use a proper Model, or just make the call itself in the Controller. The example iOS project has some great patterns to follow.
Use AFHTTPClient. If you're interacting with a particular webservice, it can be very useful to have an AFHTTPClient subclass to handle all of those requests.
I'd like to use RestKit and handle several different requests in the same class, i.e. in the didLoadResponse: method. How can I distinguish between the different requests? How do I know which request is finished?
I'm doing the request via
RKClient *client = [RKClient sharedClient];
[client get:#"/....", method] delegate:self];
Then, in the delegate-method
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
if (???) // request which gets XY returned
...
else if (???) // request which gets YZ returned
...
}
is that possible?
Sure, the RKClient get: method returns a RKRequest object. Just set a userData to the request and retrieve it later in the delegate.
RKClient *client = [RKClient sharedClient];
RKRequest *request = [client get:#"/....", method] delegate:self];
[request setUserData:#"FirstRequest"];
and check it later in the delegate
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
id userData = [request userData];
if ([userData isEqual:#"FirstRequest"]) // request which gets XY returned
...
else if (...) // request which gets YZ returned
...
}
This isn't an exact answer to your question, but I have the feeling that some people will come here wondering how to distinguish multiple requests in didLoadObjects, as I did. The solution is to use isKindOfClass.
For example, I make two HTTP calls when a user logs into my app, and I want to distinguish the object returned from the getUser call from the object returned by getSummary (because if I don't then it crashes). This code checks if the returned object is a "kind of" that particular class, and if so sets the object to a local instance of that object.
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
if ([[objects objectAtIndex:0] isKindOfClass:[APIUser class]]) {
APIUser *apiUser = [objects objectAtIndex:0];
}
else if ([[objects objectAtIndex:0] isKindOfClass:[APIUserSummary class]]) {
APIUserSummary *summary = [objects objectAtIndex:0];
}
}