HTTP/2 Push and Safari

The way I have my server set up is to basically just use the proxy directives in Caddy to proxy to a port on localhost using transparent.

Recently I’ve been adding a bit of code to some application to decide which resources might be suitable to push over HTTP/2. Because Caddy acts as a proxy to all of my applications I enabled HTTP/2 push by just adding the push directive to my config files.

In my applications I run some logic to decide which resources should be pushed and then set one Link header which contains all of the resources I want to push.

This works great pretty much everywhere except for Safari.

What happens is that a page will load part way but then hang. After some tinkering with the config files I found that if I turn off gzip then I can sometimes get a page to fully load in Safari, but refreshing the page isn’t reliable and sometimes hangs again.

Unfortunately I don’t know much else about this problem and it’s difficult for me to really collect much data about it since I don’t have a mac myself, but has anyone else had this problem, and is there a way to solve it?

I could solve it in my application code by just testing the user agent for the string "Safari" but it would be nice to avoid that kind of thing if possible.

I’m happy with any solution really, as long as I can enable HTTP/2 push for the rest of the browsers that support it. If there is a solution to make this work in Safari then that would be great, but even if the solution is to somehow turn push off for Safari only then I’d be happy to take that too.

Any help would be appreciated.

After trying to figure out a work around for this issue I haven’t had much luck.

Without having a Mac myself, and without knowing how Safari implements HTTP/2 features I can’t find a reliable way to push resources to Safari users.

In general it seems to be ok with small resources, but for larger resources it will work on and off and I have no idea why.

For now I think I’ll just get around this problem with user agent sniffing and just implement a white list of user agents for browser that I know work. It’s not ideal but it would be nice to use push and I can’t see any way of handling this nicely through Caddy.

1 Like

You say you set one Link header with all the resources… Curious whether Safari hangs if you designate resources one at a time instead?

Sorry I should have mentioned that I did test both ways. That is one Link header with all of the resources separated by commas and then one Link header per resource. I even tried only pushing one small resource per request and then after that one big resource per request.

The small resources were more reliable in Safari but still sometimes hang.

If I push one large resource (1.5MB for example) then it will load the first time if I clear my cache, but then hang when I refresh the page if I push the same resource again.

I can’t tell what the problem actually is, but my hunch is that maybe there is some kind of race condition where the browser wants to request the resource for real while the push is half way done or something like that. Of course I’m just making this explanation up as I go along since I don’t know how it’s been implemented in Safari.

I did notice after a while that the same thing happens in a couple of other browsers like, Edge and old IE versions like IE 11. I would have expected IE 11 to ignore the push requests but it hangs just like Edge and Safari.

I’m not sure if this is even really a problem that Caddy can address since I don’t know a lot about HTTP/2 at this moment in time myself.

The main reason I posted here was because I was hoping there would be some way that Caddy might detect working push support and forward a custom header on to the application to let it know if it was ok to push resources or not since I know that in Go you can use the http.Pusher type.

I don’t know if my current hunches as to why the hangs are happening are correct or not, but it could just be that I’m trying to push to browsers that just don’t support HTTP/2 push yet and rather than ignoring it they just stop.

A good line of thought. Here’s Caddy’s push request handler: caddy/caddyhttp/push/handler.go at 50ab4fe11e1a6a516a2afa060f64d67d5653c401 · caddyserver/caddy · GitHub

You can see that we cast the ResponseWriter to a Pusher, and then handle the error case for unsupported / push-disabled clients. I guess (unfortunately for you perhaps) that means we can blame net/http for this, if the server end is the cause of the problem.

Thanks for the link to relevant part of the source code. I definitely didn’t expect Caddy to be mishandling pushes, especially given the ease of checking for http.Pusher.

White listing the user agents that I know work is going well at the moment, even if it’s not the most reliable method of getting around this problem.

When I work on the push logic in our application a bit more I’ll try and run some more thorough tests and see if I can find the actual cause, but I don’t know when I’ll get the time to do that. When I do get the time I’ll post any details I find here though :slight_smile:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.