How to trace and debug an iOS crash (Part 2)
Dead end
Having reached a dead end, decided to take a look at the client side in an effort to understand more about the request/response cycle.
First thing was to enable logging for the headers and body of both the request and the response.
> GET /foo HTTP/1.1 > "Accept-Encoding" = gzip; > "Accept-Language" = [languages]; > Range = "foo=0-10"; > "User-Agent" = "[App Bundle Id]/ (unknown, iPhone OS 4.3.3, iPad, Scale/1.000000)"; < HTTP/1.1 200 OK < Content-Type: plain/text < X-pad: avoid browser bug
Notice the “Content-Type: plain/text” (though the “X-pad: avoid browser bug” did look interesting on its own way).
QUESTION: How and why the content type of the response was changing from the expected “application/json”?
EVIDENCE
1. > GET /foo HTTP/1.1 2. < [empty body] 3. Response "Content-Type: plain/text" (Application Layer)
Back to basics
Continuing with the elimination process, it was the iPad’s turn this time, substituting with curl
[curl] -> [AWS Load Balancer] -> [AWS EC2 Single instance)
In an effort to reproduce the request as sent by the iPad, failed to receive the “desired” response, instead the received response was the expected “application/json” one.
curl -v -A "[App Bundle Id]/ (unknown, iPhone OS 4.3.3, iPad, Scale/1.000000)" -H "Accept:" -H "Accept-Encoding: gzip" -H "Accept-Language" = "[languages]" -H "Range: foo=0-10" www.domain.com/foo
Bare in mind that the request headers above were obtained using
NSLog(@"%@", [request allHTTPHeaderFields])
With no access to the iOS source, it’s difficult to know if every header is indeed in the returned NSDictionary. Hence decided to bring back to the equation the iPad and go down to the Network Layer to see the raw data.
Dumping the tcp
[iPad 4.3.3 WiFi] -> [Mac] -> [AWS Load Balancer] -> [AWS EC2 Single instance)
In order to trace the network traffic as sent from the iPad, all you need is to have your Mac connected to the same network and share that network connection from OS X.
Apple -> System Preferences -> Internet Sharing "Share your connection from: Ethernet" "To computers using: Wi-Fi" "Computer name: [computer name]"
Connect the iPad to the Wi-Fi [computer name] and have tcpdump running.
sudo tcpdump -i en0 -s 0 -B 524288 -w ~/Desktop/DumpFile01.pcap
Here is what the tcpdump revealed.
> HEAD /foo HTTP/1.1 > Host: [AWS Load Balancer] > User-Agent: [App Bundle Id]/ (unknown, iPhone OS 4.3.3, iPad, Scale/1.000000) > Accept-Encoding: gzip > Accept-Language: [languages] > Accept: */* > Cookie: [cookies] > Content-Length: 0 > Connection: keep-alive > HTTP/1.1 200 OK > Date: [timestamp] > Server: [server] > Content-Range: foo=0-10/11 > Connection: Keep-alive > Content-Type: text/plain > X-Pad: avoid browser bug
followed by
> GET /foo HTTP/1.1 > Host: [AWS Load Balancer] > User-Agent: [App Bundle Id]/ (unknown, iPhone OS 4.3.3, iPad, Scale/1.000000) > Accept-Encoding: gzip > Accept-Language: [languages] > Range: foo=0-10 > Accept: */* > Cookie: [cookies] > Connection: keep-alive < HTTP/1.1 200 OK < Date: [timestamp] < Server: [server] < Content-Range: foo=0-10/11 < Content-Type: application/json < Connection: Keep-alive < transfer-encoding: chunked
Indeed there is a “plain/text” response but this is coming from a HEAD request on the same URL. What follows is the “application/json” one to the GET.
QUESTION: If the response is coming back as expected at the network layer, why are responses getting mixed up at the application layer?
Could that be a threading issue? A race condition? Why is it happening only on the iPad 4.3.3?
To make sure that this wasn’t a race condition, put a breakpoint on the completionBlock of the HEAD request to check the response. The response was the expected one.
Checking the response from GET however, turned out to be the same one as the HEAD.
QUESTION: Why would iOS 4.3.3 return a seemingly cached response?
EVIDENCE
1. > GET /foo HTTP/1.1 2. < [empty body] 3. Response "Content-Type: plain/text" (Application Layer) 4. Response "Content-Type: application/json" (Network Layer) 5. Cached response on GET /foo (Client side)
While debugging the above, came across a neat trick on how to query an http server for its version
A third part will follow