2

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?

dumbledad
  • 16,305
  • 23
  • 120
  • 273
  • It would be useful to know why my question has been down-voted, I don't think I have fallen into any obvious pitfalls? Perhaps the iOS tag is too general, I'll take that back out. – dumbledad Jul 21 '16 at 08:46

1 Answers1

1

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.

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • I've added implementations for those but they do not get called so there's no detectable difference. – dumbledad Jul 29 '16 at 10:19
  • Ah, right. You also have to remove the completion handler when you create the request, and handle the data yourself with delegate methods. Using a completion handler with a request prevents the didReceiveResponse: delegate method from ever getting called. And I'm still not 100% certain that this will solve the problem. – dgatwood Jul 29 '16 at 18:35
  • calling the completionHandler with `NSURLSessionResponseAllow` (aka `.allow`) will cause `func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)` to be called, where the actual data can be received and further processed. – mygzi Nov 19 '18 at 19:52