16

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?

oldbeamer
  • 1,285
  • 2
  • 15
  • 24
  • It's not called for HTTP status code errors, because the URL loading system does not treat them as errors. The connection itself was fine, and it returned a code that your application wishes to treat as an error, but not all may – Mike Abdullah Jun 30 '09 at 15:14

9 Answers9

11

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.

Jeff Hellman
  • 2,117
  • 18
  • 17
  • 1
    This is certainly one possible solution. It means doing the same work twice though sadly. – Mike Abdullah Jun 30 '09 at 15:13
  • Not sure why this was downvoted. Sure, it's not easy but it would work. Indeed, it (may) be the only "official" way of doing it. – Stephen Darlington Jun 30 '09 at 15:25
  • On second look, this really isn't even that hard. There's tons of sample code on line about how to setup the NSURLConnection and then grab the data using the delegate method. Try http://developer.apple.com/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html – Jeff Hellman Jun 30 '09 at 17:53
  • Note that `didReceiveData:` does not give your delegate *the* data; it gives your delegate *some of the* data. You will need to collect the whole data until the connection finishes, or work out some way to feed these incremental chunks to the UIWebView. – Peter Hosey Mar 05 '10 at 04:21
  • It is possible to hook directly into the UIWebView loading, see my response below... – joshis Apr 27 '11 at 12:41
  • the problem is that this does not always solve the problem; in my case I need specific cookies to be maintained that are in the UIWebView and will not be in the NSConnection – taxilian Nov 16 '12 at 02:33
4

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.

dsingleton
  • 976
  • 6
  • 7
  • 2
    not for UIWebView, but in other part I will try to use this few lines of code, it seems it match perfectly my needs, with synchron call. Thanks –  Mar 19 '12 at 17:48
  • 1
    Be careful on how you use the synchronous requests. It does block the UI IIRC. – dsingleton May 30 '13 at 20:38
3

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)
}
Michael Shang
  • 686
  • 8
  • 9
  • Seems to work - beware though that some of the responses weren't HTTPURLResponse:s so that crashed for me - I just avoided that by proper optional checking `if let httpresponse = response as? HTTPURLResponse { /.../` – Jonny Oct 25 '18 at 08:08
2

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;
}
Shady Elyaski
  • 499
  • 6
  • 15
2

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

Ahmet Ardal
  • 1,162
  • 1
  • 11
  • 12
1

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

Community
  • 1
  • 1
DaGaMs
  • 1,521
  • 17
  • 26
0

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.

Jelle
  • 1,024
  • 10
  • 18
-4

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.

Mike Abdullah
  • 14,933
  • 2
  • 50
  • 75
  • I didn't think it was possible to retrieve the status code from the DOM, is it in there somewhere? – oldbeamer Jul 03 '09 at 06:57
  • I have to admit that I don't know enough about Javascript to say either way. Just that there's no Obj-C API at the moment – Mike Abdullah Jul 03 '09 at 10:07
  • HTTP is a protocol. HTML is a markup language. HTTP status codes are part of HTTP response headers. You won't find HTTP status codes somewhere hiding in the DOM. – Eric G Apr 05 '13 at 00:20
-6

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"];
}
Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55