Generating POST messages using AFNetworking? - ios

So, I get JSON to return from my web service using a command like this in terminal:
curl --data 'method=my-service.search&document_type=x&keywords=y' http://mywebsite.com/services/json/my-service.search
And I've been trying to get this data through AFNetworking for parsing and use in an app. So far, this is what I have tried:
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:#"document_type", #"keywords", nil];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
I set the URL string to http://mywebsite.com/services/json/my-service.search and the dictionary is supposed to contain the parameters for searching.
But I get an error saying that the request failed due to "unacceptable content-type: text/html" (obviously, it wants JSON).
So my question is as follows: How do you perform a cURL data request through AFNetworking?
I have a bonus question as well: how do you interact with the parameters in the dictionary (say, set the keyword field to "Stack Overflow" or something like that)?
Thanks for the help! I'm just starting out, so any advice would be appreciated.

You can specify content-type with this:
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
But first of all make sure you set propert request serializer:
manager.requestSerializer = [AFJSONRequestSerializer serializer];
It should set content type automatically
Also use POST: instead of GET:

Related

SOAP Client Generator for Objective-C from WSDL?

I'm stuck on a SOAP integration project. I need to call some SOAP Web Services from an iPad app.
I have the WSDL, and I need to generate the Objective-C classes to invoke the service calls.
The Mac developer library has an "Example: Calling a SOAP Operation with HTTP Authentication", but the sample code it links to is broken (SOAP_AuthExample.dmg, 404).
The Web Services Core Programming Guide even says (emphasis added):
"Currently, OS X provides no high-level support for extracting SOAP functions from WSDL files. You can use NSXMLParser to read a WSDL file into memory from a URL, however. It is then relatively simple to search the file for a particular method name or to find a URL associated with a service. With a bit more effort, you can extract method names, data types, and parameter names to build a SOAP call. You can then use the Web Services Core framework to make SOAP calls and parse the responses."
I've also found the following three generators; however, the first is throwing an error about some missing files, the second's code generates and endless stream of ARC errors when I try to build (and a refactor is not working), and the third is paywalled.
http://easywsdl.com/
https://code.google.com/p/wsdl2objc/
http://sudzc.com/
Can anyone steer me in the right direction?
I was in your same situation a couple of months ago, and I'm currently working in an iPad app that requires to connect to a SOAP service. I already have the classes, but they are specific for my project and I'm not authorised to share it. However, I'm making a more generic Objective-C class (in any case the final solution for all the SOAP servers) to share it with the world on Github.
First of all you have to set the SOAP message. It has a common structure like this:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
// here comes the header information: credentials, connection information. If needed, of course
</soap:Header>
<soap:Body>
// here comes the request parameters. In my case, these parameters are related to a resource called SOAPAction (That identifies the method to be called. e.g getListOfClients). I don't know if it is the same in all servers
</soap:Body>
</soap:Envelope>
I say to you, the company for which I'm making the app, has provided me with the methods and authentication information. I don't know if this is your case, but you have here a general glance of what to do.
I use AFNetworking to send the request, in this way:
NSString *messageLength = [NSString stringWithFormat:#"%lu", (unsigned long)[msg length]];
NSURL *requestURL = [NSURL URLWithString:self.requestURL];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:requestURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];
[theRequest addValue:[requestURL host] forHTTPHeaderField:#"Host"];
[theRequest addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[theRequest addValue:soapAction forHTTPHeaderField:#"SOAPAction"];
[theRequest addValue:messageLength forHTTPHeaderField:#"Content-Length"];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:[msg dataUsingEncoding:NSUTF8StringEncoding]];
// sending the request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:theRequest];
operation.responseSerializer = [AFHTTPResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *xml = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"Take the data master Yoda: %#", xml);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Bad SOAP server!. Error: %#", error.description)
}];
[[NSOperationQueue mainQueue] addOperation:operation];
self.requestURL is the server URL, obviously. If the request was successful, then you have the server's xml response ready to parse. If not, a request error description otherwise.
This page helped me a lot to find out a solution for some issues that I encountered, and it is related to the website http://sudzc.com/ that you quotes above:
http://blog.exadel.com/working-with-ios-and-soap/
I hope this helps in any way.

Dealing with unexpected response content types with AFNetworking / AFHTTPRequestOperation

I'm using AFNetworking to process HTTP requests in my iOS app. I have run into a stumbling block. I cannot be certain of what the response content type will be, but you have to set the response serializer BEFORE the request is processed. This means I could do an API request, expecting an image back, but actually there's some authentication error, so the server returns a JSON-formatted response instead.
Here's my code:
AFHTTPRequestOperation* op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setResponseSerializer:[AFJSONResponseSerializer serializer]]; // ??????
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSDictionary* result = (NSDictionary*) responseObject;
onSuccess((NSArray*) result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
onFailure(error);
}];
[op start];
As you can see, I've had to set the expected content type implicitly by setting the responseSerializer to [AFJSONResponseSerializer serializer]. So if I get something else back, it causes an error, even though I may still wish to parse an process that response when dealing with the error.
So my question is, should I just use the standard AFHTTPResponseSerializer, examine the response status code and then process the response body manually (as json, xml, html an image etc)?
Set the accepted content types you want on the serialiser with acceptableContentTypes:
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
serializer.acceptableContentTypes = [NSSet setWithArray:#[#"text/plain", #"text/html"]];
[op setResponseSerializer:serializer];
From the docs:
By default, AFJSONSerializer accepts the following MIME types, which
includes the official standard, application/json, as well as other
commonly-used types:
application/json
text/json
You don't have to use AFJSONResponseSerializer, as you can create your own serializer as long as it conforms to the AFURLResponseSerialization protocol.
If you have JSON responses but XML error responses, you could just subclass AFHTTPResponseSerializer and do your own processing in there.
You could also use AFCompoundResponseSerializer to parse different response types just going through the serializers you give it.
Your API is a little unusual: if you aren't authorized, it should just use an HTTP 401 response, not JSON. But there's plenty of unusual API's out there and I bet you don't have control over this one.
The fix is straightforward:
Make an implementation of AFURLResponseSerialization that just acts as a proxy, and assign that one as the serializer for your request. When the response comes in, have it take a quick look at the data and then instantiate and call the right serializer.

AFNetworking 2.0 - unexpected NSURLErrorDomain error -1012

We ran into the following issue with our app that uses AFNetworking 2.0.
When using AFHTTPRequestOperationManager's GET method, we got an error NSURLErrorDomain code -1012. The request used HTTPS and the server does not require user authentication. The request never reached the server by the way.
We have run several tests and this is the first time the error was produced and we are wondering how this error can get produced because it does not seem relevant.
Setup of AFHTTPRequestOperationManager :
httpOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:
[NSURL URLWithString: HTTPS_URL)]];
httpOperationManager.responseSerializer =
[AFXMLParserResponseSerializer serializer];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled: YES];
GET REQUEST
AFHTTPRequestOperation *op =[httpOperationManager GET:
[NSString stringWithFormat:SOME_PATH]
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code to setup NSXMLParser ...
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", [error localizedDescription]);
}];
I think you already solved the problem, but if you are trying to authenticate in a server that doesn't have a valid certificate you have to set YES for property allowInvalidCertificates in your AFHTTPRequestOperationManager object:
[yourManager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"your_username" password:#"your_password"];
[yourManager.securityPolicy setAllowInvalidCertificates:YES];
Also, as #a1phanumeric said, it can be necessary to include this line:
[yourManager.securityPolicy setValidatesDomainName:NO];
Cheers.
NSURLErrorDomain -1012 is NSURLErrorUserCancelledAuthentication. (See the error code list and search for -1012.)
You state, "the server does not require user authentication". But this error would not be called if that were true.
Possible causes:
Your server is erroneously requesting authorization (a server bug)
The URL formed with HTTPS_URL and SOME_PATH is not what you expect, and some other server is requesting authorization
Some intermediary (like a proxy server, or an access point) is requiring authorization.
Some debugging tips:
Set breakpoints inside the AFNetworking implementation to see which URL is being hit
Configure AFHTTPRequestOperationLogger so you can see the actual request body and response in your console log
Make the same request with curl or Advanced Rest Client and observe the server's response
Side note: I think [NSString stringWithFormat:SOME_PATH] is pointless - why not just use SOME_PATH?

How to do a JSON encoded GET request with AFNetworking-2.0?

I'm trying to use a pattern similar to this post which describes doing a PUT/POST for RESTful API, but for a GET.
My original code looks almost the same, except I used a GET: keyword. I quickly discovered that rather than sending a JSON body, it instead url encodes the parameters. This is not mentioned in the documentation of the AFJSONSerializer class. You have to go to the superclass documentation (AFHTTPSerializer) and read through its properties, where you'll find the one about HTTPMethodsEncodingParametersInURI. By default that set is populated with HEAD, GET, and DELETE. So for those types of requests, the JSON serializer apparently reverts to its parent class for behavior?
So I put together the following code:
AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
manager.securityPolicy.allowInvalidCertificates = YES;
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.requestSerializer.HTTPMethodsEncodingParametersInURI = [NSSet set];
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername: currentUser() password: currentPassword()];
[manager
GET: #"https://172.16.214.214:44321/trees"
parameters: [NSDictionary dictionary]
success:^(NSURLSessionDataTask* task, id responseObject){
NSLog(#"Response: %#", responseObject);}
failure:^(NSURLSessionDataTask* task, NSError* error){
NSLog(#"Error: %#", error);}];
The line that sets HTTPMethodsEncodingParametersInURI = [NSSet set] is intended to let me get JSON encoded parameters like I wanted for the GET too. Unfortunately, I see nothing at the server when I use this and get the following in my error console:
2013-12-10 10:11:14.149 myValve[957:60b] Error: Error Domain=NSURLErrorDomain Code=-1005 "The
network connection was lost." UserInfo=0x17e5d4b0
{NSErrorFailingURLStringKey=https://172.16.214.214:44321/trees,
NSErrorFailingURLKey=https://172.16.214.214:44321/trees, NSLocalizedDescription=The network
connection was lost., NSUnderlyingError=0x17e53240 "The network connection was lost."}
What am I still missing?
The rationale behind AFNetworking's behavior here is probably most concisely explained in the Stack Overflow question, HTTP GET with request body. One of those answers quotes Ray Fielding:
... In other words, any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics.
So, yes, you can send a body with GET, and no, it is never useful to do so.
The HTTP/1.1 spec defines GET to "retrieve whatever information (in the form of an entity) is identified by the Request-URI" and does not contemplate information to be included in the body of the request. So the use of the body in GET request technically might not be prohibited, but, at best, it is non-standard. AFNetworking's choice not to support it is not entirely surprising.
So, you might not want to put JSON in the body for a GET requests. One would generally add the parameters to the URL. If you want to send JSON in the body of the request, then do a POST.
Sounds like it can't connect to the server, have you tried to make a call outside iOS?
For reference I normally make a JSON request like this:
NSURL *url = #"https://172.16.214.214:44321/trees";
NSData* data = [NSData dataWithContentsOfURL: url];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];

User Login With AFNetworking

I am building my first iOS app.
I have got the backend code done, but I am struggling with the Objective-C part of it.
I have a signup / login page.
But I don't know how to send that data to my server using Objective C.
I have read that AFNetworking is good, but I was wondering how I could use that for user login .
I have downloaded and added AFNetworking to my XCode Project and set up headers.
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://examplewebsite.com]];
[client setDefaultHeader:#"key" value:#"value"];
[client setAuthorizationHeaderWithUsername:#"username" password:#"password"];
[client setAuthorizationHeaderWithToken:#"token"];
NSURLRequest *request = [client requestWithMethod:#"someMethod" path:#"somePath" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
but I am still lost.
Since you're trying to login to your own API, you don't want setAuthorization stuff. That's for basic HTTP auth. Instead you want to use getPath:parameters:success:failure or the postPath version, depending on if your backend is expecting HTTP GET or HTTP POST.
Pass your userid / password in the parameters argument. You should set parameterEncoding to be the correct format. You're probably using HTTP Forms url encoding, or JSON. Whatever your backend expects.
You don't want to set the authorization headers in this case, since this is for "basic access HTTP authentication", which is a method for a HTTP user agent to provide a user name and password when making a request to a server.
You want to use your own API and interact with a restful server and therefore, I would recommend, that you subclass AFHTTPClient -> interact with an API, Web Service, or Application. - Take a look at the examples in the AFNetworking zip archive, if you have difficulties in subclassing AFHTTPClient.
Since you want to create an app with user login, the app needs to send these information to your server, and the server should return if the login was succesful or not.
This can be done like so - HTTP POST.
- (void)login {
// Login information from UITextFields
id params = #{
#"username": self.usernameField.text,
#"password": self.passwordField.text
};
//Call the AFHTTP subclass client, with post block. postPath = the path of the url, where the parameters should be posted.
[[theAuthAPIClient sharedClient] postPath:#"/login"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//handle succesful response from server.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// handle error - login failed
}
}];
}
You need to pass the parameters in the right format, depending on what format your server expects. This can be done by setting the right encoding in your AFHTTPClient subclass -> ParameterEncoding
Since I came here while searching for a working solution for AFNetworking 2.0, not knowing that AFHTTPClient was removed from the Framework, I will post the new way to establish this connection here:
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:#"http://examplewebsite.com"]];
[manager setRequestSerializer:[AFHTTPRequestSerializer serializer]];
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"userName" password:#"password"];

Resources