NSUrlConnectionDelegate - Getting http status codes - iphone

in iOS, how can I receive the http status code (404,500 200 etc) for a response from a web server. I am assuming it's in the NSUrlConnectionDelegate.
Objective-C or Monotouch .NET answer ok.

Yes, you can get status code in delegate method -didRecieveResponse:
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];
}

NSHTTPURLResponse* urlResponse = nil;
NSError *error = nil;
responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
The aSynchronous request should also have a way to get the NSHTTPURLResponse..
You get the status code like this:
int statusCode = [urlResponse statusCode];
int errorCode = error.code;
In the case of some much used error codes (like 404) it will get put in the error but with a different code (401 will be -1012).

Here's how to do it in MonoTouch for .NET for those C# users. THis is in the NSUrlConnectionDelegate.
public override void ReceivedResponse (NSUrlConnection connection, NSUrlResponse response)
{
if (response is NSHttpUrlResponse)
{
var r = response as NSHttpUrlResponse;
Console.WriteLine (r.StatusCode);
}
}

Looking at this other stackoverflow question it looks like you can handle http status codes in the - (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)response delegate method:
- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse*)response
{
if ([response isKindOfClass: [NSHTTPURLResponse class]])
statusCode = [(NSHTTPURLResponse*) response statusCode];
}

Related

NSURLConnectionDownloadDelegate method not getting called

i m working on a project that connect to server and download data from it. i like to support resume download if connection gets interrupt. my approach is to save the downloaded portion of data to a destination file; and if connection gets interrupted, i want to mark the downloaded portion using connection:didWriteData:totalBytesWritten:expectedTotalBytes and later resume from the stopped portion with server.
my code:
- (IBAction)connectToServer:(UIButton *)sender
{
// setup url and send request to server
NSURL *url = [NSURL URLWithString:BASED_URL];
self.urlRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:self.urlRequest delegate:self];
// start receive data if connection established
if (self.urlConnection){
self.receivedData = [NSMutableData data];
NSLog(#"starting to receive data");
} else {
// handle error
NSLog(#"failed to connect to server");
}
}
- (void)doSomethingWithData
{
// handle data here
}
#pragma NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// received data
[self.receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// error connection
NSLog(#"connection failed");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Data receiving succeed, received: %d bytes of data", [self.receivedData length]);
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes
{
NSLog(#"not getting called");
}
my question is how come the "connection:didWriteData:totalBytesWritten:expectedTotalBytes" method never get called?
thanks so much!
chris
Have u added this in .h file:
#interface yourViewContoller : UIViewController <NSURLConnectionDataDelegate, NSURLConnectionDelegate>
According to Apple's documentation:
The NSURLConnectionDownloadDelegate protocol describes methods that should be implemented by the delegate of instances of NSURLConnection created using Newsstand Kit’s downloadWithDelegate: method.
Also, it says:
If you are using NSURLConnection directly, your delegate class should instead implement the methods defined in the NSURLConnectionDataDelegate protocol.

unable to parse JSON data from a NSURLConnection response

I am getting a server response of the form:
results are:{
AverageMark = 40;
"Grade A" = 10;
"Grade B" = 20;
"Grade C" = 30;
"Grade D" = 20;
MaxMark = 99;
MinMark = 44;
ProfileGrade = "";
ProfileMark = 1;
}
However I am unable to save the response data into an Array.
This is my code inside didReceiveResponse:
{
NSString *jsonString = [[NSString alloc] initWithString:responseData];
NSArray *jsonResults = [jsonString JSONValue];
NSLog(#"results are:%#",jsonResults); //this log is shown above
for (int i=0; i<[jsonResults count]; i++)
{
NSDictionary *AllData=(NSDictionary *)[jsonResults objectAtIndex:i]; //Program is crashing here--//
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
NSString *avgMarkString;
avgMarkString=(NSString *)[AllData objectForKey:#"MaxMark"];
[DataArray addObject:avgMarkString];
}
}
I want to save the response data into the array called "DataArray". But the program is crashing.
What am I doing wrong?
You likely don't have the complete data yet in -connection:didReceiveResponse:. Create an instance variable or property of the type NSMutableData and initialize the data ivar or property in
-connection:didReceiveResponse: if you get a valid statusCode (between 200-299 should be ok). Use appendData: on the data object in the -connection:didReceiveData: delegate method. Finally in -connectionDidFinishLoading: the data is complete and can be parsed into JSON.
Alternatively you could just use the AFNetworking library. The library got some convenience methods for dealing with XML, JSON, images, etc...
Read the following page to get an introduction into the capabilities of AFNetworking: http://engineering.gowalla.com/2011/10/24/afnetworking/
Some example code from one of my own projects for downloading using a queue using NSURLConnectionDelegate methods. The URL Request objects are a custom subclass of NSURLConnection for some block "callbacks":
#pragma mark - URL connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSRange range = NSMakeRange(200, 99);
if (NSLocationInRange(httpResponse.statusCode, range));
{
self.data = [[NSMutableData alloc] init];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// inform caller that download is complete, provide data ...
if (_request.completionHandler)
{
_request.completionHandler(_data, nil);
}
[self removeRequest:_request];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
DLog(#"%#", error);
// inform caller that download failed, provide error ...
if (_request.completionHandler)
{
_request.completionHandler(nil, error);
}
[self removeRequest:_request];
}
that isn't json, try having a look at this http://json.org/example.html
Given JSON response is invalidate. Validate your JSON response here.

UrlConnection Iphone

I am trying to get data from a server using the following code.
+ (NSString *) getData:(NSString *)methodName parameters:(NSDictionary *) parameters error:(NSError **)error
{
NSString *body = [UdoziProxy getRequestBody:methodName parameters:parameters];
NSMutableURLRequest *request = [UdoziProxy createRequest:body];
// Send the request .
NSHTTPURLResponse *urlResponse = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:error];
if (responseData == nil || error !=nil) {
return nil;
}
return [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
}
It works fine when the server is running but when I stop the server deliberately, the responseData object has still value and is not nil. How can I handle the situation where either the connection is lost or the server is down ?
You need to set the cache policy on your NSURLRequest object. If the synchronous API still doesn't honour that, you need to switch to using the asynchronous API and throw away the cache request object when you receive it. If that doesn't work, then maybe your network has a transparent proxy which is doing some caching for you.
you have to implement NSURLConnection delegates in
you can get failure status in
the following delegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
responseData =nil
}
there you have to make the recieved data to nil value
also its safe to
get data in
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{ }
which indicates that the connection is finished

Multiple NSURLConnection & NSRunLoop

I am trying to speed up my application download speed. I used Asynchronous NSURLConnection to download contents from the server, it was working fine with one connection.
I use the code from this post to implement multiple delegate objects. Multiple NSURLConnection delegates in Objective-C
When I created 2 NSURLConnection objects, each one is trying to download different files.
The callback didReceiveData routine was called but the it only received data of the first NSURLConnection object until the first connection was done then it started to receive the data from the second NSURLConnection. I want these two connections to receive data at the same time,what should I do? Here is my current code.
-(IBAction) startDownloadClicked :(id) sender
{
while (bDownloading)
{
int nCurrentCon = 0;
while (nCurrentCon < 2)
{
[self downloadAFile:[filenameArray objectAtIndex:nCurrentCon]];
nCurrentCon++;
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
}
- (void) downloadAFile: (NSString*) filename
{
NSString* urlstr = #"ftp://myftpusername:password#hostname";
NSURLRequest* myreq = [NSURLRequest requestWithURL:[NSURL URLWithString:urlstr]];
DownloadDelegate* dd = [[DownloadDelegate alloc] init]; //create delegate object
MyURLConnection* myConnection = [[MyURLConnection alloc] initWithRequest:myreq delegate:dd
startImmediately:YES];
}
Then in my Delegate Object, I implemented these routines
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receiveBuffer setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"receiving data for %#", targetFileName); //the file name were set when this delegate object is initialized.
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Download Failed with Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"File %# - downloaded.", targetFileName);
}
Your code looks okay. I have a similar setup that works successfully (although there seems to be a limit of four concurrent conections).
The main difference between your and my code is that you use FTP while I use HTTP. Why don't you try it with HTTP connections just to see whether you have run into a restriction of FTP connections on the iPhone?

How do I get the last HTTP Status Code from a UIWebView?

I would like to detect when a page load request give to a UIWebView has returned a status code in the 5xx or 4xx range.
I've setup the delegate for the web view and have provided a -webView:didFailLoadWithError:error method but although it gets called fine for timeouts, it is not called for HTTP Status Code errors.
Any suggestions?
Hmmm... I'm not an iPhone developer, but....
Could you try creating an NSURLRequest with the URL you want to load? Then you could make the connection using NSURLConnection.
NSURLConnection has a delegate method
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
which will give the the response from the server. Please note that if you are making the connection over HTTP, the response will actually be of class NSHTTPURLResponse. The NSHTTPURLResponse can be used to get the status using the following instance method
- (NSInteger)statusCode
NSURLConnection has another delegate method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
that can be used to get the data from the URL Connection. You could then manually load the data into your UIWebView using:
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL
That seems like a ton of work, but it could be done. Hopefully someone else will come up with the easier way, though I don't see it.
I struggled with this for quite a while trying to find a good answer. The requirements that I was working under was that I needed to be able to determine the status of the FIRST page load, and any load after that I would assume that the user was clicking links which shouldn't be broken (not guaranteed, I know, but a lot better than the alternatives).
What I ended up doing was making the initial call myself via a NSURLConnection (synchronously), and then passing the data on to the UIWebView.
NSURL *googleURL = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *googleRequest = [NSURLRequest requestWithURL:googleURL];
NSHTTPURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:googleRequest
returningResponse:&response
error:&error];
if ([response statusCode] >= 400 || error)
{
// handle error condition
} else {
[webView_ loadData:responseData MIMEType:[response MIMEType]
textEncodingName:[response textEncodingName]
baseURL:[response URL]];
[self setView:webView_];
}
If you desire to get the information for every request, you could simply use the method
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
to intercept all requests and make them yourself. You would have to have some kind of request management technique, because when you call the loadData on the UIWebView, it will invoke the shouldStartLoadWithRequest callback, and you want to make sure you don't do an infinite loop of making the same request over and over.
I struggled very hard on this topic when things are on Swift 3.0 now. I even created a custom URLProtocol and tried to intercept all web requests, just to realize eventually that it was unnecessary. The reason for the confusion for me is because that they moved the didReceiveResponse function:
optional public func connection(_ connection: NSURLConnection, didReceive response: URLResponse)
to NSURLConnectionDataDelegate, which inherits from NSURLConnectionDelegate.
Anyway, Here is the Swift 3.0 version that works:
// You first need to have NSURLConnectionDataDelegate on your UIWebView
// MARK: - get HTTP status code
// setup urlconnectiondelegate
// so that the didReceiveResponse is called
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
let conn: NSURLConnection? = NSURLConnection(request: request, delegate: self)
if conn == nil {
print("cannot create connection")
}
return true;
}
// intercept the actual http status code
func connection(_ connection: NSURLConnection, didReceive response: URLResponse) {
let httpResponse: HTTPURLResponse = response as! HTTPURLResponse;
print(httpResponse.statusCode)
}
Currently, UIWebView does not provide any functionality for getting HTTP status codes for the requests it loads. One workaround is to intercept the request loading process of UIWebView using the UIWebViewDelegate methods and use NSURLConnection to detect how the server responds to that request. Then you can take an appropriate action suitable for the situation. This article explains the workaround in detail on a demo project.
And you don't need to continue loading the request after you received a response. You can just cancel the connection in - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response method after learning the HTTP status code. This way you prevent the connection from loading any unnecessary response data. Then you can load the request in UIWebView again or show an appropriate error message to the user depending on the HTTP status code, etc.
Here is the article
and here is the demo project on github
Here's a work-around to get HTTP response code, but with sending just one request to each URL:-
BOOL isLoad;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSLog(#"Requesting: %# - %d - %#", request.URL.absoluteString, navigationType, request.URL.host);
if (navigationType != UIWebViewNavigationTypeOther) {
//Store last selected URL
self.loadedURL = request.URL.absoluteString;
}
if (!isLoad && [request.URL.absoluteString isEqualToString:loadedURL]) {
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError || ([response respondsToSelector:#selector(statusCode)] && [((NSHTTPURLResponse *)response) statusCode] != 200 && [((NSHTTPURLResponse *)response) statusCode] != 302)) {
//Show error message
[self showErrorMessage];
}else {
isLoad = YES;
[_wbView loadData:data MIMEType:[response MIMEType]
textEncodingName:[response textEncodingName]
baseURL:[response URL]];
}
}];
return NO;
}
isLoad = NO;
return YES;
}
As I just posted on another thread, it is also possible to intercept any NSURLRequest at the level of the NSURLProtocol and create your NSURLResponse there, instead of in your UIWebView delegate/controller. The reason why this is preferable in my opinion is that it maintains the back/forward navigation stack of the UIWebView. The outline of the approach can be found in this excellent blog post by Rob Napier:
http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588
and there's code on GitHub:
https://github.com/rnapier/RNCachingURLProtocol
Here is a nice example where they use a combination of creating a NSURLConnection for the first loading, and the UIWebView for the next pages:
http://www.ardalahmet.com/2011/08/18/how-to-detect-and-handle-http-status-codes-in-uiwebviews/
Basically this is the main trick, using the YES/NO return value of shouldStartLoadRequest:
- (BOOL) webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
if (_sessionChecked) {
// session already checked.
return YES;
}
// will check session.
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
if (conn == nil) {
NSLog(#"cannot create connection");
}
return NO;
}
This should get you going without using synchronous requests, especially combined with Jeff's answer.
Sadly at present it looks like the best available option is to use -stringByEvaluatingJavaScriptFromString: to run some sort of small script that queries the document for its status code.
What about implementing webViewDidFinishLoad: on your UIWebViewDelegate, and using the request property of the UIWebView to access the headers you're interested in? Something like this (untested):
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString* theStatus = [[webView request] valueForHTTPHeaderField:#"Status"];
}