33

I have a problem similar to the one described in the link below.

NSHTTPURLResponse statusCode is returning zero when it should be 401

I use [NSURLConnection sendSynchronousRequest:returningResponse:error:] to get data from a server.

When NSURLConnection receives the HTTP Code 401, it does not return anything but an error object with code -1012 from the NSURLErrorDomain. -1012 corresponds to NSURLErrorUserCancelledAuthentication. Since I have to parse the HTTP Header I need to get the original error and not what NSURLConnection made out of it.

Is there a way to receive the original 401 http packet?

Community
  • 1
  • 1
Tomen
  • 4,854
  • 5
  • 28
  • 39
  • Have you looked into ASIHTTPRequest? – Moshe Jan 13 '11 at 20:29
  • 1
    I'd rather stick with built-in APIs if possible. FWIW, checking the `NSError` out-param for `NSURLErrorUserCancelledAuthentication` works fine. It just seems odd that when a status 401 is returned, the `NSURLResponse` out-param is left nil. – Marc W Jan 13 '11 at 23:30

5 Answers5

20

Yes. Stop using the synchronous API. If you use the asynchronous delegate-based API then you have a lot more control over the connection. With this API, except in cases where an error is encountered before the HTTP header is received, you will always receive -connection:didReceiveResponse:, which gives you access to the HTTP header fields (encapsulated in an NSURLResponse object). You can also implement authentication using the relevant delegate methods if you are so inclined.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • Doing authentication with the delegate message is unnecessarily chatty, especially since the credentials are always the same and required for each request in my app. So I just add the Authentication header myself to the `NSURLRequest` before sending the first time. All I need back is the response code and possibly the (very short) response body data. This particular request is also one of several sequential steps in my algorithm, and doing it the asynchronous way would make that sequence much less clear. – Marc W Jan 13 '11 at 23:29
  • 1
    If you're going to use NSURLConnection, the asynchronous API is the only way to get the data you want. Your only alternative is to use a third-party implementation of HTTP. – Lily Ballard Jan 13 '11 at 23:59
  • 1
    i will mark this as answer... although there is an "overhead" associated with making it asynchronous, i think in the long run the control you have over the process pays back and you still can wrap the whole process to make it seem synchronous – Tomen Jan 14 '11 at 11:18
  • 12
    If you use the iOS5+ [NSURLConnection sendAsynchronousRequest:queue:completionHandler:], this again becomes a problem. – conradev Jun 20 '12 at 00:01
18

Workaround:

[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

NSHTTPURLResponse *aResponse = (NSHTTPURLResponse *)response;
int statusCodeResponse = aResponse.statusCode;

NSString *strError = [NSString stringWithFormat:@"%@", [connectionError description]];
if ([strError rangeOfString:@"Code=-1012"].location != NSNotFound) {
    statusCodeResponse = 401;
   }

Is not the best solution, but it works!

11

I'm surprised at the "recommended" answer to this thread.

Fine, I'm sure using the async version methods are better, but it still doesn't explain why the sendSynchronousRequest function does let you pass in a variable to return the response code, but in some circumstances, it just returns nil.

It' 4 years since this issue was reported, I'm using XCode 6.2 with iOS 8.2, and this ancient bug is still present.

My web services deliberately return a 401 error when a user's username & password aren't correct.

When my iPhone app calls this service (with the wrong credentials)...

NSHTTPURLResponse *response = nil;
NSData *data = [ NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error ];

..response is returned as nil (so I can't test for the HTTP Response 401), error receives a -1012 message wrapped up like this:

    Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" 
    UserInfo=0x174869940 {NSErrorFailingURLStringKey=https://mywebservice.com/Service1.svc/getGroupInfo/6079, NSUnderlyingError=0x174e46030 
    "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1012.)",
NSErrorFailingURLKey=https://mywebservice.com/Service1.svc/getGroupInfo/6079}

and the sendSynchronousRequest function returns a lengthy XML string containing... well... this...

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Request Error</title>
    <style>BODY { color: #000000; background-color: white; font-family: Verdana; margin-left: 0px; margin-top: 0px; } #content { margin-left: 30px; font-size: .70em; padding-bottom: 2em; } A:link { color: #336699; font-weight: bold; text-decoration: underline; } A:visited { color: #6699cc; font-weight: bold; text-decoration: underline; } A:active { color: #336699; font-weight: bold; text-decoration: underline; } .heading1 { background-color: #003366; border-bottom: #336699 6px solid; color: #ffffff; font-family: Tahoma; font-size: 26px; font-weight: normal;margin: 0em 0em 10px -20px; padding-bottom: 8px; padding-left: 30px;padding-top: 16px;} pre { font-size:small; background-color: #e5e5cc; padding: 5px; font-family: Courier New; margin-top: 0px; border: 1px #f0f0e0 solid; white-space: pre-wrap; white-space: -pre-wrap; word-wrap: break-word; } table { border-collapse: collapse; border-spacing: 0px; font-family: Verdana;} table th { border-right: 2px white solid; border-bottom: 2px white solid; font-weight: bold; background-color: #cecf9c;} table td { border-right: 2px white solid; border-bottom: 2px white solid; background-color: #e5e5cc;}</style>
  </head>
  <body>
    <div id="content">
      <p class="heading1">Request Error</p>
      <p>The server encountered an error processing the request. The exception message is 'Access is denied.'. See server logs for more details. The exception stack trace is: </p>
      <p>   at System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize(MessageRpc&amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp; rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)</p>
    </div>
  </body>
</html>

Come on Apple, fix your bugs...

Mike Gledhill
  • 27,846
  • 7
  • 149
  • 159
0

Its actually quit simple. Now it is true that the async request is actually a pretty good helper. But you have to keep in mind that its just a helper. Http is just a protocol for networking and therefor it MUST not interact with the user. In other words both cases are actually one and the same.

If you do not want to use the asynch helper than i suggest you show the user a login dialog and repeat your request until the user presses cancel.

[edit]

just for info, personally i prefer curl. Works like a charm almost everywhere ;)

0

I was facing the issue not receiving 401 Unauthorized error code (was getting nil response also didReceiveResponse method was not getting called) instead receiving -999 cancelled error. The mistake I was doing in my code is: In didReceiveChallenge: delegate method,

if (challenge.previousFailureCount == 0)
{
    //handle certificate trust
    completionHandler (NSURLSessionAuthChallengeUseCredential, newCredential);
}
else
{
    completionHandler (NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}

The NSURLSessionAuthChallengeCancelAuthenticationChallenge was resulting in the -999 error and there was no 401 response obtained. Instead when I used completionHandler (NSURLSessionAuthChallengePerformDefaultHandling, nil); I was receiving 401 response in didReceiveResponse: delegate method as expected.

Note from iOS documentation https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html, If we cancel the urlsession then it will be reported as cancelled, but not as 401 error in response.

Note: NSURLSession does not report server errors through the error parameter. The only errors your delegate receives through the error parameter are client-side errors, such as being unable to resolve the hostname or connect to the host. The error codes are described in URL Loading System Error Codes.
Server-side errors are reported through the HTTP status code in the NSHTTPURLResponse object. For more information, read the documentation for the NSHTTPURLResponse and NSURLResponse classes.
Lakshmi
  • 176
  • 1
  • 10