I need to check and evaluate the HTTP Status Codes in my iPhone app. I've got an NSURLRequest object and an NSURLConnection that successfully (I think) connect to the site:
// create the request
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
receivedData=[[NSMutableData data] retain];
} else {
// inform the user that the download could not be made
}
I got this code here: https://web.archive.org/web/20100908130503/http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html
But it doesn't (as far as I can see) say anything about accessing HTTP Status codes.
How to make a connection to a site and then check the HTTP Status Codes of that connection?
This is how I do it:
#pragma mark -
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int responseStatusCode = [httpResponse statusCode];
}
But I'm using an asynchronous NSURLConnection, so this may not help you. But I hope it does anyway!
Related
I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!
I am retrieving data via NSURLRequest and it is working perfectly. I have added password protection to that directory now and I am not sure how to configure my code to add in the password for the directory (myStuff)
Can anyone tell me how this is done based on my current code?
Thank you
-(void) retrieve
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://XXXXXXX.com/myStuff/test.php"]cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:8.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
[self showServerAlert];
return;
}
}
//CALL THE OTHER DELEGATE METHODS
The standard URL syntax would be:
http://username:password#XXXXX.com/myStuff/test.php
When doing this though you should not use http. You really want to use https. And don't hardcode the password in your code. A hacker could easily figure it out.
The standard examples I'm seeing in iOS to do a simple GET HTTP connection is to:
NSString *finalURLstring = #"http://www.somesite.com?value=2";
NSURL *url = [NSURL URLWithString:finalURLstring];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLConnection *urlConnection = [NSURLConnection connectionWithRequest:request delegate:self];
Then implement these functions:
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
-(void) connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
-(void) connectionDidFinishLoading:(NSURLConnection*)connection
And if you have multiple connections you just create multiple NSURLConnections and compare them in connectionDidFinishLoading:
if (connection == urlConnection1)
// do something
else if (connection == urlConnection2)
// do something else
The issue with this is that you are created a new connection to the server each time right? Is it possible to create your NSURLConnection and reuse it? i.e. keep the connection open so you can make multiple calls instead of creating new connections on every call to the server?
Thanks for your help,
-David
NSURLConnection does that already. It reuses the existing connections.. Check out this accepted answer in
this post. You'll get your answer for your question..
I am making an jsonstring. When i execute it, it works when i do it in my browser. I do this by logging the exact url and copy it in the browser. Than i get the HTTP Get that i want, but in the iPhone i only get a Bad Login.
- (IBAction)getDown:(id)sender { //perform get request
NSLog(#"beginnen met versturen");
//NSString * _barCode = [[NSUserDefaults standardUserDefaults] objectForKey:#"phoneNumber"];
//build up the request that is to be sent to the server
//NSString*jsonString = [[NSString alloc] initWithFormat:#"{\"barcode\":\"%#\"}", _barCode];
NSString*jsonString = [[NSString alloc] initWithFormat:#"{\"barcode\":\"123456\"}"];
NSString *str = [NSString stringWithFormat: #"http://server.nl/scan.php?data=%#",jsonString];
NSLog(#"%#", str);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:str]];
NSLog(#"url: %#", request);
[request setHTTPMethod:#"GET"];
// [request addValue:#"getValues" forHTTPHeaderField:#"METHOD"]; //selects what task the server will perform
NSLog(#"met value: %#", request);
//initialize an NSURLConnection with the request
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(!connection){
NSLog(#"Connection Failed");
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ // executed when the connection receives data
receivedData = data;
/* NOTE: if you are working with large data , it may be better to set recievedData as NSMutableData
and use [receivedData append:Data] here, in this event you should also set recievedData to nil
when you are done working with it or any new data received could end up just appending to the
last message received*/
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ //executed when the connection fails
NSLog(#"Connection failed with error: %#",error.localizedDescription);
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
/*This message is sent when there is an authentication challenge ,our server does not have this requirement so we do not need to handle that here*/
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"Request Complete,recieved %d bytes of data",receivedData.length);
//[self.delegate requestReturnedData:receivedData];//send the data to the delegate
NSData *data = receivedData;
NSDictionary *dictionary = [NSDictionary dictionaryWithJSONData:data];
NSLog(#"%#",dictionary.JSONString ); ; // set the textview to the raw string value of the data recieved
NSString *value1 = [dictionary objectForKey:#"barcode"];
NSLog(#"%#", value1);
NSString *value2 = [dictionary objectForKey:#"product"];
NSLog(#"%#",dictionary);
NSLog(#"%#", value2);
}
Here's the log:
2013-01-10 16:31:46.550 Scanner[14875:907] http://server.nl/scan.php?data={"barcode":"123456"}
2013-01-10 16:31:46.551 Scanner[14875:907] url: <NSMutableURLRequest (null)>
2013-01-10 16:31:46.553 Scanner[14875:907] met value: <NSMutableURLRequest (null)>
**2013-01-10 16:31:46.556 Scanner[14875:907] Connection failed with error: bad URL**
When i delete the complete json from the string i get no bad url. So there might be the problem. Anyone know what i am doing wrong?
You need to encode it, before perfoming an URL request.
Best and most elegant solution would be adding a category over NSString for example, something like this:
- (NSString*)URLEncode {
// Should not be encoded:-_.
return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, CFSTR(";/?:#&=+$,!*'()<>#%\"{}|\\^[]`~"), kCFStringEncodingUTF8) autorelease];
//
}
And when you make the request:
NSString *str = [NSString stringWithFormat: #"http://server.nl/scan.php?data=%#",jsonString];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[str URLEncode]];
If you don't want to use additional files (even thought that would be recommended), add this method to your class:
- (NSString*)URLEncode:(NSString )yourURL {
// Should not be encoded:-_.
return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, CFSTR(";/?:#&=+$,!'()<>#%\"{}|\\^[]`~"), kCFStringEncodingUTF8) autorelease];
}
and use
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[self URLEncode:str]];
I don't have much information right now, I apologize, I'm in a bit of a hurry and just saw your question. But I saw your question and I remember working on a project which was essentially an HTML-based remote control for the iphone, and when the user clicked on some of the buttons for the remote, it followed the urls that opened up identical pages but had server-side code to instruct the server to pause, play, stop, etc... I DO remember that the iPhone had a bug that caused it to not be able to parse all of my URLs, even though they were correctly formatted and worked on a desktop client. That is why I switched over to POST requests (where user clicks instead activated javascript functions that set hidden form variables and then submitted forms rather than directly navigating to long URLS). Anyways, I know this may not directly apply to you, but the point is that I did find a bug in the iPhone's URL parsing, so it might not be your fault. I'll look up any new information I can find a little later. Good luck.
In my iPad application, I want to load a modal window with some data.
But those data can be retrieved from a web service call. So, I have created another class and in that class's connectionDidFinishLoading I can have the response data. As the web service call is asynchronous, I have to wait for the data to load the modal window. Can anyone help me with some example code? Should I think in different way?
Thank you all for the prompt reply.
My problem was solved using the NSNotificationCenter. This tutorial was helpful http://www.youtube.com/watch?v=WB-QCv_4ANU&feature=plcp
Either you can load modal window from connectionDidFinishLoading method. Or you can use delegates to pass data from connectionDidFinishLoading metod to the window that you are going to present. Refer this tutorial.
You start the connection this way:
NSURL *url = [NSURL URLWithString:<#your url string#>];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
myData = [[NSMutableData alloc] init];
con = [[NSURLConnection alloc] initWithRequest:request delegate:self];
And you need to implement NSURLConnectionDelegate delegate.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//append data to your NSMutableData object
[myData appendData: data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
//handle the error
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//here you can use your NSMutableData object, fill your window with the data etc.
<#your code#>
}
This is just an example. You can read more about it in NSURLConnectionDelegate Protocol Reference.