Where's the response data from a 500 error in NSURLSession?

1.7k Views Asked by At

I have the following Swift code in my iOS app:

session?.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) -> Void in
    if let httpResponse = response as? NSHTTPURLResponse {
        var dataString:String?
        if data != nil {
            // Print the response as a string
            dataString = String(data: data!, encoding: NSUTF8StringEncoding)!
            if RequestManager.verbose { print("Response data: \(dataString)") }
        } else {
            if RequestManager.verbose { print("Response data: \(response?.description)") }
        }
        //[...]

When the calls to the web-service API succeed this works fine: response!.statusCode is set correctly and the data passed into the completion handler can be parsed into useful objects from JSON.

However when the server response is an error, e.g. 500, then data is zero bytes and all I can see is the header information, not the body of the response.

Here's an example response, gleaned from the simulator using the proxy application Charles:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=00000000000000000000888888888888888888888888aaaaaaaaaaaaaaaaaaaa;Path=/;Domain=blah-testing.azurewebsites.net
Date: Thu, 21 Jul 2016 05:57:44 GMT

{"ExceptionType":"Exception","Message":"Not on your Nelly","StackTrace":" at Blah.Controllers.CustomersController.Get() in D:\Users\timregan\Source\Repos\ServerSideCode\Blah\ApiControllers\CustomersController.cs:line 37\r\n at lambda_method(Closure , Object , Object[] )\r\n at Microsoft.AspNetCore.Mvc.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__28.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__18.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware1.<Invoke>d__18.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware1.d__18.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Blah.Startup.<>c.<b__6_0>d.MoveNext() in D:\Users\timregan\Source\Repos\ServerSideCode\Blah\Startup.cs:line 59","StatusCode":500}

But in Xcode the same response has far less. The header elements are all there in the NSHTTPURLResponse object but the other parameters to the dataTaskWithRequest completion handler have nothing: data is zero bytes and error is nil. Where has the body of the response with the server error stack trace in it gone? How do I access that in Swift?

1

There are 1 best solutions below

3
dgatwood On

IIRC, by default, NSURLSession stops requesting data after it gets a non-200 header, so that it doesn't waste time downloading body data that you probably don't need.

If you want to actually retrieve the body data every time:

  • Implement the URLSession:dataTask:didReceiveResponse:completionHandler: delegate method and make it always return NSURLSessionResponseAllow.
  • Implement the URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler: method and make its completion handler always pass NULL as the new request so that redirects won't be followed.

I think that should be sufficient, unless I'm forgetting another delegate method somewhere.