A Little Help Debugging Chuked + multipart/x-mixed-replace Responses

# Assaf Inbal (7 days ago)

Hey,

I’m trying to debug an issue where Safari (both on macOS and iOS) fails to load an MJPEG stream in some cases. The HTTP response is of content type "multipart/x-mixed-replace” and is also using chunked encoding. The stream works as expected in Firefox and Chrome but fails in Safari. In the console I can see the following “errors”:

[Error] The operation couldn’t be completed. ( error 0.) [Error] Cannot load .

A bit more background information (see home-assistant/home-assistant#13995, home-assistant/home-assistant#13995): the HTTP response is an MJPEG stream created by a project called “Home Assistant”, in addition the response is proxied by an nginx server. When pointing Safari to the stream directly, not via the proxy, it works. I’ve also tested this with another server (jacksonliam/mjpg-streamer, jacksonliam/mjpg-streamer) proxied by the same nginx server which does work as expected but I’ve yet to find any crucial difference between the two responses that would make it not work.

Here’e an example of the server response:

HTTP/1.1 200 OK

Server: nginx/1.10.3 Date: Tue, 09 Apr 2019 14:59:54 GMT Content-Type: multipart/x-mixed-replace; boundary=--frameboundary Transfer-Encoding: chunked Connection: keep-alive Strict-Transport-Security: max-age=31536000; includeSubdomains Cache-Control: private Cache-Control: no-cache Cache-Control: no-store

Looking at the network dump, I can see the packets are all accepted until the second frame/boundary is received and is then reset by the client. The content-length header seems correct. I’m thinking maybe Safari/WebKit doesn’t parse this connection correctly or there is some quirk in the response so I figured that if I can find why the connection is cancelled on WebKit’s side, I could understand the root cause.

I went ahead and compiled WebKit and tried to understand the flow of things but it’s taking me quite some time so perhaps anyone here could point me in the right direction.

So far, the lowest point I reached was a call to WebResourceLoader::didReceiveResponse() with has a status of 200 and needsContinueDidReceiveResponseMessage is false. After this the request is canceled (it reaches ResourceLoader::cancel() with a null error, i.e error.isNull() is true).

Can anyone please point me to where in the code chunked and/or multipart responses are parsed with the default configuration (not using curl, etc.) so that I might understand why it rejects the stream?

Thanks in advance!

Contact us to advertise here
# Assaf Inbal (4 days ago)

Hey again,

For what it’s worth, I did manage to figure out that the connection is dropped/canceled at Source/WebCore/loader/SubresourceLoader.cpp:didReceiveResponse() where it fails the condition that the resource is a multipart but it’s not detected as an image: "canceling load because something about a multi-part non-image”.

Not sure why it wouldn’t be detected as an image as the response frame does include a “Content-Type: image/jpeg” header and the data + length are valid. Where is it “decided” if a resource is an image? Does it only rely on the HTML tag that caused the resource to be loaded? Response headers? Actually trying to decode the data?

Want more features?

Request early access to our private beta of readable email premium.