I'm writing an iPad app that needs to communicate with a backend server. The first order of business in using this backend is to login, and for this the server has a URL that we can POST to, which I do like this:
// Create the request.
NSString* loginURL = #"http://foo.local/signature/service/auth/rest/firewall/login";
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:loginURL]];
NSString* credentials = #"{\"userName\":\"foo2#foolinator.com\", \"password\":\"password\"}";
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[credentials dataUsingEncoding:NSASCIIStringEncoding
allowLossyConversion:YES]];
// Logging in...
NSError* error = nil;
NSURLResponse* response;
NSData* result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
NSString* responseString = [NSHTTPURLResponse localizedStringForStatusCode:[httpResponse statusCode]];
NSLog(#"Response String is: %#\n", responseString);
NSLog(#"Header fields are: %#\n", [httpResponse allHeaderFields]);
What's odd is that the response I'm getting is Error 405: Method Not Allowed. I would've expected this if I was doing a GET, but I'm doing a POST.
I installed WireShark to examine the HTTP requests and it seems that there's actually two being made. The first one, is a POST call, and the server returns some cookie information as a response, and then a second GET call, which is what the code above gets back.
Why does this happen? Is it something to do with the response from the server the first time?
While you research your web service's login API, a couple of unrelated observations:
You should be sending this asynchronously if doing this from the main queue. Never issue synchronous network requests from the main queue. If you do this synchronously on the main queue (a) you risk having the iOS watch-dog process kill your app, which happens if the main queue becomes unresponsive while some synchronous network request is being processed; and (b) it's a bad UX to simply freeze an app during a network request ... if you need, disable the UI and show an indeterminate progress indicator (a UIActivityIndicatorView) while the network request is in progress.
You should probably be setting a value forHTTPHeaderField for Content-Length. It's probably not required, but it's good practice.
You probably should not be using a string with the JSON with the userid and password, but rather you should probably build this from a NSDictionary using something like NSJSONSerialization. As it is, if your password, for example, had any characters that needed to be escaped (e.g. a quotation mark), the existing code might not work. Using NSJSONSerialization is an easy way to ensure that your JSON is properly formatted.
You probably should not be sending a password in plaintext in your JSON request. At the very least, I hope your server employs HTTPS.
Anyway, with these observations, assuming your server really is expecting a JSON request, I might suggest something like:
// hopefully your production server is employing HTTPS
NSString *loginURL = #"https://foo.local/signature/service/auth/rest/firewall/login";
// use NSJSONSerialization to create JSON rather than building it in a NSString
NSDictionary *postDictionary = #{#"userName": userName, #"password": password}; // assuming you have NSString variables, `userName` and `password`
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:postDictionary options:0 error:&error];
NSAssert(postData, #"dataWithJSONObject failed: %#", error);
// when creating request, also set Content-Length
NSURL *url = [NSURL URLWithString:loginURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
[request setValue:#"application/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
// issue request asynchronously
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(#"sendAsynchronousRequest error: %#", connectionError);
return;
}
// process the server response here
}];
You might still want to use a NSURLConnectionDataDelegate/NSURLConnectionDelegate based request (you can identify redirects, challenges, cancel it if you need, etc.), but the above might be a good start at an asynchronous JSON-based request.
Related
I came to the last stage of development of my app and here is the bit that I've never done before.
My friend has developed and API for my app to send and receive data, using django rest framework.
I need to authenticate my app to connect to it, send some data and receive data.
What I have found so far is:
NSURL *url = [NSURL URLWithString:#"http://localhost:8080/my/path/to/api/login/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", #"myUsername", #"myPassword"];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", authData];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
//------------------------------------------------------------------------------------------------------------------------------------//
//EDIT: Added this based on answer form #Quver.
NSURLResponse *response1;
NSError *responseError;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response1 error:&responseError];
if (result.length > 0 && responseError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:result
options:0
error:NULL];
NSLog(#"Got response form server: %#", greeting);
}
This equals output like:
<0a0a3c68 746d6c3e 0a0a2020 20203c68 6561643e 0a202020 20202020 200a2020 20202020 20200a20 20202020 + ~50 lines of similar stuff. Hope this helps.
//-----------------------------------------------------------------------------------------------------------------------------------------//
I guess this is the way to create request. What should I do next? And how do I know that I have connected?
Then, if I have connected, how do I get data form there? (I have a url that gives me json as output - this is what I want to get). Assume the url to be http://localhost:8080/url/that/gives/json/.
Thank you for any help. Hope this is enough information for the question. I will add anything else required.
NSURLResponse *response;
NSError *responseError;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&responseError];
Add this to get response. You already prepared request, now it's time to send it with NSURLConnection. I you sync request insted of async, becouse of using GCD for whole metod request + sqlite update.
After a few days of searching I have found a way.
First, we need a token for authentication. I am generating it through terminal for now:
curl -X POST -d "grant_type=password&username=<your username>&password=<your password>" http://<client secret>:<client id>#url/to/token/page
Then, in your view controller where you want to connect:
//always put </> at the end of link
NSURL *aUrl = [NSURL URLWithString: #"http://where/you/trying/to/conect"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
[request addValue:[NSString stringWithFormat:#"<type> <your token>"] forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
NSError *error = nil;
self.response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error: &error];
This will return any json that the page supposed to return. This might not be the most secure way, but this is exactly what I need for now. I will look at more secure solutions like OAuth 2, later.
Hope this helps to someone.
I have a bit of code for sending/receiving data from a webpage that works ~90% of the time. However the ~10% of the time it doesn't really bothers me, and I can't understand what's going on because the code isn't changing, and I have a similar android implementation that works flawlessly. Here's what I have.
- (NSString*) postData:(NSString*) url params:(NSString*) params
{
NSURL* urlRequest = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[urlRequest standardizedURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSError* err = nil;
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
if (!err) {
return [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];;
}
return nil;
}
- (NSString*) getData:(NSString*) url
{
NSURL* urlRequest = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[urlRequest standardizedURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSError* err = nil;
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
if (!err) {
return [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];;
}
return nil;
}
I call getData to make the initial connection to the website. It has some information I parse out (two security tokens to send in when I send postData) and then send the data through the POST request. This first part always works everytime. However I then send another POST request to change pages again (after parsing out any new security tokens), and this is the part that fails ~10% of the time. By fail, I mean the website responds that my session has been compromised. I know this message happens if the security tokens are invalid, but I've checked those over and over and over and everytime they're fine. My theory is that I'm loosing cookie information, but from what I understand NSURLConnection should store the cookie information based on domain.
If I open a browser and go to the page, then delete the only cookie that is stored however, it takes me back to the login page (like expected) and doesn't give me the error message about my session being corrupted.
Any ideas would be greatly appreciated
I am trying to authenticate to a HTTP RESTful site using NSURLRequest from an iPhone app. I put the user/pass encoded in base64 and pass it in as the "Authorization" header in the NSURLRequest with the word "Basic " appended to it. However, I keep getting an auth error in the other end. Here is my code. If I do a manual CURL request, it works. I also checked to make sure the Base64 is properly encoded and user, pass, and url are correct.
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
NSString *basicAuthCredentials = [NSString stringWithFormat:#"%#:%#", user, pass];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", AFBase64EncodedStringFromString(basicAuthCredentials)];
authValue = [authValue stringByReplacingOccurrencesOfString:#"\n" withString:#""];
[urlRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
[urlRequest setHTTPMethod: #"GET"];
NSData *httpData = [self encodeDictionary:data];
[urlRequest setHTTPBody:httpData];
NSHTTPURLResponse *response;
NSError *error;
NSData* result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
The auth code looks fine. Sure, the stringByReplacingOccurrencesOfString is unnecessary, but doesn't hurt. And I might use the native base64 methods (NSData methods base64EncodedDataWithOptions or base64EncodedStringWithOptions in iOS 7, base64Encoding in iOS versions prior to 7) rather than AFNetworking's, but I don't think this is the problem either.
Assuming your userid and password are correct and that your server really is using basic authentication (which I assume is correct if your cURL is succeeding), the only thing that is obviously incorrect here is the call to setHTTPBody for a GET request. NSURLConnection will not send the body for a GET request (nor should it, as the body is semantically meaningless in GET requests). If you want to send a request with a body, you should generally use POST. Or if you want to send parameters in a GET request, you can add them to the URL, but not to the body.
I am attempting to access the JIRA REST webservice using iOS. I have managed to get the requests to work, but the response is not JSON. I get a response with a session id and everything from the login request that comes before this one, and this request returns a similar response, but the data coming back that is suppose to be JSON but looks like : <61737369 616e2e6e 65742f73 65637572 652f7072 (continues for several lines)>. I have made the same requests with JMeter and it gets returned a JSON string, but this request in iOS does not.
NSURL *url = [NSURL URLWithString:#"https://company.atlassian.net/rest/
api/2/project"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url2];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type" ];
[request setHTTPMethod:#"GET"];
//Encoded authorization header
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
[NSURLConnection sendAsynchronousRequest:request queue:
[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse
*response, NSData *data, NSError *error){ }];
I have attempted to manipulate the Content-Type trying random different things, but none have worked. Any ideas on how to get it to return JSON?
You're setting the Content-Type header, but you don't appear to be sending any content. It seems more like you should be setting the Accept header.
You can't log an NSData instance and expect to see the contents, even if the content is a string. When you log, either try to convert the data into a string (alloc, initWithData:) or deserialize the JSON (JSONObjectWithData:options:error:).
I have a Windows based REST Server built using Microsoft-HTTPAPI (i.e. its not running under IIS)
If I send a JSON request to the service from a browser based REST tool (Firefox RESTClient) the server receives the request and processes it correctly as per the traces from the Microsoft Service Trace Viewer and the response from the service. I receive valid JSON back from the service.
I have an iOS application that uses a NSURLConnect to send the VERY SAME JSON request to the service, but it always times out. I've traced using WireShark, and the HTTP request is correctly formed and sent correctly to the server in both cases. I've traced using the MS Trace Viewer and it goes into "receive Bytes" but never returns. I get an exception when I eventually close the WCF Server.It never returns when I close the iOS app.
I have tried using NSURLConnection sendSynchronousRequest and asynchronously using the callbacks and asynchronously using completion blocks. If I make the timeout 0, the call (to sendSynchronousRequest) never returns. In all other cases, I get a timeout, and the WCF never completes the receive bytes part of the WCF invocation (a POST request)
I've checked
Here's my iOS Code:
dispatch_queue_t myQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(myQ, ^{ // First Queue the parsing
#try {
//Generate endpoint url
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",appState.posPostingURL,serviceName]];
//Setup request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:10.0];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", #"xxxx", #"xxxx"];
NSString *authData = [NSString base64StringFromString:authStr];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", authData];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
//Setup request headers
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"no-cache" forHTTPHeaderField:#"Cache-Control"];
[request setValue:[NSString stringWithFormat:#"%d", [dataBuffer length]] forHTTPHeaderField:#"Content-Length"];
// Put the request data in the request body
[request setHTTPBody: dataBuffer];
NSHTTPURLResponse *urlResponse = nil;
NSError *error = nil;
// and send to the server synchronously
serverData = [[NSURLConnection sendSynchronousRequest:request
returningResponse:&urlResponse
error:&error] mutableCopy];
// Now check the status and response
if (error) {
My WCF Code is as follows
namespace MyWebService{
[ServiceContract(Name = "MyServiceIntegrator", Namespace = "")]
public interface IMyIntegrator
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/Configure",
BodyStyle=WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
Result Configure(MyServerConfig config);
In the app.config file, the service endpoint is configured with
binding="webHttpBinding"
The Trace view is as follows:
Could there be any reason on either side that the server does not complete the read even though the JSON is valid.
I have also checked NSURLConnection JSON Submit to Server
and as far as I can see my code to send the request is correct.
Any ideas - Been pulling my hair out for 3 days now
What happens if you change this:
[request setValue:[NSString stringWithFormat:#"%d", [dataBuffer length]] forHTTPHeaderField:#"Content-Length"];
To this:
[request setValue:[NSString stringWithFormat:#"%i", [dataBuffer length]] forHTTPHeaderField:#"Content-Length"];
Probably won't make a difference, but the rest of your code looks fine so maybe worth a shot. (My thinking is the decimal is causing the server issues).
ferdil,I think you can try changing timeout period to 30.0 instead of 10.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30.0];