Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTTP byte range protocol client behaviour on iPad/iPhone

I was testing a HTTP servlet implementation (kindly shared by BalusC) which supports HTTP byte range requests.

I have found some peculiar differences between different HTTP clients, and was wondering if I am not missing anything. I have used a >2G mp4 video file for my tests and was capturing packets with Wireshark. This is roughly what happens:

  • Samsung Galaxy SII:

    • HTTP GET request for file comes, asking for byte range [0; <almost the end of the file>]
    • server responds, starts to stream the file
    • each subsequent chunk is served within the bounds of the same HTTP response. No new HTTP request is sent (unless the video is fast forwarded to a certain position). Streaming code chunk for this is quite simple, it reads RandomAccessFile input and writes to OutputStream output via byte[] buffer:

      while ((read = input.read(buffer)) > 0) {
          output.write(buffer, 0, read);
      }
      
  • iPad 1
    • HTTP GET request for file comes, asking for byte range [0; <almost the end of the file>]
    • server responds, starts to stream the file
    • iPad gets a chunk or two and then unilaterally decides to stop accepting bytes from the server and issues a separate GET request for the next chunk of the file. New range boundaries are e.g. [100, almost the end of the file]. The video is shown OK.
    • cycle repeats again from step 2. Left boundary always moves towards the end of the file.

I didn't investigate how exactly the connection is terminated. It could be that iPad stops sending TCP ACK packets, I suppose this doesn't really matter that much.

My problem is that for every terminated connection I get java.net.SocketException: Broken pipe exception. This not only pollutes the logs (which is a minor/solvable problem) but I believe this can hurt performance as raising exceptions is quite expensive. When watching simple video, the exception rate was around 1 exception/sec, but if the server had 100 concurrent users, then the JVM would probably be spending loads of time just calculating stack traces instead of doing real work.

I have also tested this on iPhone using iOS 6 and was able to observe the same behaviour as iPad 1. Just to reiterate, this does not happen on Samsung Android nor any desktop browsers I have tried, including Safari on a desktop Mac.

Questions:

  • is this a know bug/feature of iPad/iPhone?
  • is there a workaround for this?
like image 998
mindas Avatar asked Sep 28 '12 09:09

mindas


4 Answers

IIRC, "broken pipe" simply means the other side received data after it closed its read end.

The most reasonable thing I can think of is that it's an attempt to not waste large amounts of bandwidth downloading video that never gets watched (perhaps something they've agreed with carriers, which I suspect is the reasoning behind the "live streaming" restriction:

"Video streaming content over a cellular network longer than 10 minutes must use HTTP Live Streaming and include a baseline 64 kbps audio-only HTTP Live stream."

The only other easy way to throttle the download is to stop read()ing and wait for the receive window to fill up, but that's not always easy to do (NSURLConnection really doesn't make this easy, for example).

If you're exceptionally lucky, the client will close its write end (such that the server will read() EOF) and wait for a little while before closing its read end. In this case, it might be safe to assume that the client no longer wants the rest of the download. RFC 2616 is a little hazy (it seems to forget that sockets can be closed in only one direction) but mentions "graceful close" (which according to Microsoft involves closing the write side and finishing reading from the read side until a timeout passes), but also says

Servers SHOULD NOT close a connection in the middle of transmitting a response, unless a network or client failure is suspected.

So if you know it's an iDevice and you read EOF, then it might be safe for the server to close the socket, provided you've thoroughly tested that it doesn't break anything — changing HTTP behaviour depending on User-Agent just seems like a terrible idea.

Alternatively, don't care. You can do U-A sniffing and ignore the exception if it's an iDevice (which seems less terrible than changing HTTP behaviour). The exception overhead is almost certainly negligible, and probably far lower than the overhead of printing it to a log. 100 exceptions a second is nothing. Profile it if you're unsure.

You could also file a bug with Apple, but as these things go, it's not particularly dubious network behaviour.

like image 122
tc. Avatar answered Nov 15 '22 21:11

tc.


You need to send a Accept Ranges header when streaming on iPhone. This may be causing your problem. Look at this post

MP4 plays when accessed directly, but not when read through PHP, on iOS

like image 31
Ascii Dude Avatar answered Nov 15 '22 22:11

Ascii Dude


First of all,

spending loads of time just calculating stack traces

is not going to calculate if you are not gonna call get/printStackTrace(). turn it off the log or catch it and avoid it somewhere.

I had the same problems, it didn't go away for me either. Well, those are really stupid choices to make but you can use a load balancer which is accepting connections and redirecting it your server tomcat or glassfish whatever you are using. i observed this lack of broken-pipe behavior when i started to use ELB on AWS. NGINX or Apache could do some frontier communication for you.

I am saying this because even the operating system could be the reason why JVM doesn't receive proper TCP communication shutdown because of the JVM implementation on the OS.

like image 21
Hayati Guvence Avatar answered Nov 15 '22 23:11

Hayati Guvence


Coming back on our shared finding. Take a look at this discussion on the apple website. It seems that now this issue has been causing problems with iOS6 consuming too much data while streaming.

(In Dutch language but exactly the same issue reported here. Android doing 1 request, iOS doing multiple requests)

Time to re-test this stuff with iOS6.0.1 to see if they indeed FIXED the range request problems.

Just tested on my iPod Touch 5th Gen - iOS6.0.1: The range request is still requesting 0-1, then a few times 0-full file followed by smaller ranges. However it still looks messy

like image 39
bart s Avatar answered Nov 15 '22 23:11

bart s