You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Safari (macOS 12, iOS 15) and compression is broken #1347
All versions of uWS are totally incompatible with iOS 15 when using compression and will not keep a connection. This is because of an Apple bug where client_no_context_takeover is not properly implemented in newest Safari for iOS 15.
Because of this, if you use compression and expect iOS 15 connections, you will have to enable uWS::DEDICATED_DECOMPRESSOR as well.
See EchoServer.cpp example. uWS::DEDICATED_DECOMPRESSOR is available in v20.3.0 and forward. This adds about 44kb of memory usage per connection.
And a comment that gives ?hope? of an apple response.
Alex Christensen 2021-12-16 19:59:07 PST
Alberto, thank you for the excellent go program to reliably reproduce an issue with multiple fragments and the extension combination of extensions "permessage-deflate; server_no_context_takeover; client_no_context_takeover". I found what I believe was a misplaced call to zlib's inflateReset in a non-WebKit framework and am working to fix it internally, tracked by rdar://85078119
And also relevant issues logged against the go gorilla/websocket implementation, further confirming that its a client issue. gorilla/websocket#731 gorilla/websocket#696
The fix if there is one, to the "misplaced call to zlib's inflateReset", has not yet turned up in my installation of a beta release of IOS 15.3(19D5026g)
This pull request #1399 contains an example, CompressionIOS15, if I have done it correctly, that shows that the use of the compression option DEDICATED_DECOMPRESSOR | DEDICATED_COMPRESSOR is not enough for a stable connection to an IOS 15.3(19D5026g) Iphone.
In my investigations I instrumented the calls to forceClose() in WebSocketProtocol.h:consumeMessage() as below.
CompressionIOS15, serves some simple html and javascript at test1.html that reproducibly results in the server aborting the connection, calling forceClose(), finding "wState->state.opStack == -1". I haven't figured out the meaning of opStack yet. That is hampering my progress bit level decoding of what is going on at the moment.
It is flaky. Sometimes giving up immediately, other times it may take various combinations of messages.
I watch the phones Safari console using a connected Desktop Safari Develop Web Inspector.
Big initial messages seem to trigger a forceClose more readily.
Sending multiple messages, "quickly", also seems to be a trigger.
I cannot trigger the issue with Deskop Safari Technology Preview Release 137 (Safari 15.4, WebKit 16613.1.11.8) on Big Sur.
Looks to me using the IOS15Compression tests #1399 that the current release of IOS 15.4(19E5209h) public beta, installed on my IPhone, now has working compression.
Note: I have of course NOT incorporated the user agent hasBrokenCompression function above into my tests.
It works(tm) for me, for both shared and dedicated compression as shown below.
This is the updated version of hasBrokenCompression():
// ua-test.cpp// g++ -std=c++17 -o ua-test ua-test.cpp && ./ua-test
#include<string_view>
#include<charconv>// just for testing:
#include<string>
#include<vector>
#include<iostream>
#include<chrono>/* Safari 15.0 - 15.3 has a completely broken compression implementation (client_no_context_takeover not * properly implemented) - so we fully disable compression for this browser :-( * see https://github.com/uNetworking/uWebSockets/issues/1347 */boolhasBrokenCompression(std::string_view userAgent) {
size_t posStart = userAgent.find(" Version/15.");
if (posStart == std::string_view::npos) returnfalse;
posStart += 12;
size_t posEnd = userAgent.find('', posStart);
if (posEnd == std::string_view::npos) returnfalse;
unsignedint minorVersion = 0;
auto result = std::from_chars(userAgent.data() + posStart, userAgent.data() + posEnd, minorVersion);
if (result.ec != std::errc()) returnfalse;
if (result.ptr != userAgent.data() + posEnd) returnfalse; // do not accept trailing charsif (minorVersion > 3) returnfalse; // we target just Safari 15.0 - 15.3if (userAgent.find(" Safari/", posEnd) == std::string_view::npos) returnfalse;
returntrue;
}
intmain() {
std::vector<std::string> testUserAgents = {
// Firefox 96.0:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
// Firefox Mobile 96.0:"Mozilla/5.0 (Android 12; Mobile; rv:96.0) Gecko/96.0 Firefox/96.0",
// Chrome 96.0.4664:"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
// Chrome Mobile 97.0.4692:"Mozilla/5.0 (Linux; Android 11; SM-A405FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.87 Mobile Safari/537.36",
// Edge 97.0.1072:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69",
// Safari 14.1.2 (not affected):"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15",
// Safari 15.0 (BROKEN!):"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
// Safari 15.2 (BROKEN!):"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15",
// Safari 15.3 (BROKEN!):"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15",
// "Safari 15.4" (hopefully fixed):"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15",
// "Safari 16.0" (hopefully fixed):"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
// Mobile Safari 13.4 (not affected):"Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.4 Mobile/15E148 Safari/605.1.15",
// Mobile Safari 13.4 with DuckDuckGo (not affected):"Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15",
// Mobile Safari 15.0 (BROKEN!):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
// Mobile Safari 15.1 (BROKEN!):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1",
// Mobile Safari 15.1 with DuckDuckGo (BROKEN!):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 DuckDuckGo/7 Safari/604.1",
// Mobile Safari 15.3 (BROKEN!):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Mobile/15E148 Safari/604.1",
// "Mobile Safari 15.4" (hopefully fixed):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1",
// "Mobile Safari 16.0" (hopefully fixed):"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
// Samsung Internet 16.0 (Chromium based):"Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-A320FL) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/16.0 Chrome/92.0.4515.166 Mobile Safari/537.36",
// Yandex Browser 22.1.0 (Blink based):"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 YaBrowser/22.1.0.2500 Yowser/2.5 Safari/537.36",
// parser crash tests:"",
" Version/15.",
" Version/15. ",
" Version/15.XX",
" Version/15.XX ",
" Version/15.0 ",
" Version/15. Safari/",
" Version/15.XX Safari/",
" Version/15.0 Safari/",
" Version/15.3 Safari/",
" Version/15.4 Safari/",
" Version/15.3X Safari/",
" Version/15.0x3 Safari/",
" Version/15.-1 Safari/",
" Version/15.99999999999999999999 Safari/",
};
for (auto testUserAgent : testUserAgents) {
std::cout << testUserAgent << " --- " << (hasBrokenCompression(testUserAgent) ? "BROKEN!" : "Fine") << std::endl;
}
// Benchmark:
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
constint count = 100000;
for (int i = 0; i < count; i++) {
for (auto testUserAgent : testUserAgents) {
hasBrokenCompression(testUserAgent);
}
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Time for " << count * testUserAgents.size() << " iterations: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
}
Output:
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0 --- Fine
Mozilla/5.0 (Android 12; Mobile; rv:96.0) Gecko/96.0 Firefox/96.0 --- Fine
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 --- Fine
Mozilla/5.0 (Linux; Android 11; SM-A405FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.87 Mobile Safari/537.36 --- Fine
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69 --- Fine
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15 --- Fine
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15 --- BROKEN!
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15 --- BROKEN!
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15 --- BROKEN!
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15 --- Fine
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15 --- Fine
Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.4 Mobile/15E148 Safari/605.1.15 --- Fine
Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15 --- Fine
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 --- BROKEN!
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1 --- BROKEN!
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 DuckDuckGo/7 Safari/604.1 --- BROKEN!
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Mobile/15E148 Safari/604.1 --- BROKEN!
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1 --- Fine
Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1 --- Fine
Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-A320FL) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/16.0 Chrome/92.0.4515.166 Mobile Safari/537.36 --- Fine
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 YaBrowser/22.1.0.2500 Yowser/2.5 Safari/537.36 --- Fine
--- Fine
Version/15. --- Fine
Version/15. --- Fine
Version/15.XX --- Fine
Version/15.XX --- Fine
Version/15.0 --- Fine
Version/15. Safari/ --- Fine
Version/15.XX Safari/ --- Fine
Version/15.0 Safari/ --- BROKEN!
Version/15.3 Safari/ --- BROKEN!
Version/15.4 Safari/ --- Fine
Version/15.3X Safari/ --- Fine
Version/15.0x3 Safari/ --- Fine
Version/15.-1 Safari/ --- Fine
Version/15.99999999999999999999 Safari/ --- Fine
Time for 3600000 iterations: 2057ms
ghost added a commit that references this issue on Feb 14, 2022
@gaby23s are you sure, you are testing this very specific issue with Safari compression? This issue has nothing to do with putting Safari into the background ... in this scenario, Safari disconnects immediately after connect.
We use react, setup the websocket to start closed and attach an onerror() function. In the App componentDidMount() we reattach the onmessage(), onopen(), onclose() first and THEN call reconnect(). With IOS 15.4 this has again been working fine for us.
We also use React Native WebView(effectively safari) and have had many attempts to try to use native network information to know when it is worth attempting to reconnect, none of that got us anywhere reliably, and I suspect that privacy/anit-tracking improvements in IOS further limit the effectiveness of that.
Full disclosure - Be sure that you are really reconnecting to the correct place!!!
We had a long time nasty race condition due the accidental use of default connection parameters. We were failing to "await" the download and use runtime of configuration of the websocket connection details. Depending on the users phone, state of their browser cache, network latency the reconnection would race between the default configuration connecting to development servers(firewalled) rather than the runtime downloaded configuration environment(test, qa, prod).
Activity
[-]iOS 15 and compression is broken[/-][+]Safari (macOS 12, iOS 15) and compression is broken[/+]not-implemented commentedon Nov 9, 2021
Thanks for documenting this ... we ran into the same issue :-(
It really sucks ... as using
DEDICATED_DECOMPRESSORis not an option for us because of the massive RAM usage for ten thousands of connections ...Would be nice if you can keep us up2date if there are news on the apple issue.
not-implemented commentedon Jan 4, 2022
Still no news from Apple here, right? That's really annoying ;-)
ewak commentedon Jan 11, 2022
Here is what I think is the relevant issue in the webKit bugzilla
REGRESSION (iOS 15): Websocket connection instance in javascript client getting closed
https://bugs.webkit.org/show_bug.cgi?id=228296#c37
And a comment that gives ?hope? of an apple response.
Here is the link to the gist of the "go program" mentioned above - https://gist.github.com/afcapel/4e1012bd658f6818e70115178edd3489
And also relevant issues logged against the go gorilla/websocket implementation, further confirming that its a client issue.
gorilla/websocket#731
gorilla/websocket#696
The fix if there is one, to the "misplaced call to zlib's inflateReset", has not yet turned up in my installation of a beta release of IOS 15.3(19D5026g)
ewak commentedon Jan 13, 2022
This pull request #1399 contains an example, CompressionIOS15, if I have done it correctly, that shows that the use of the compression option DEDICATED_DECOMPRESSOR | DEDICATED_COMPRESSOR is not enough for a stable connection to an IOS 15.3(19D5026g) Iphone.
In my investigations I instrumented the calls to forceClose() in WebSocketProtocol.h:consumeMessage() as below.
CompressionIOS15, serves some simple html and javascript at test1.html that reproducibly results in the server aborting the connection, calling forceClose(), finding "wState->state.opStack == -1". I haven't figured out the meaning of opStack yet. That is hampering my progress bit level decoding of what is going on at the moment.
It is flaky. Sometimes giving up immediately, other times it may take various combinations of messages.
I watch the phones Safari console using a connected Desktop Safari Develop Web Inspector.
Big initial messages seem to trigger a forceClose more readily.
Sending multiple messages, "quickly", also seems to be a trigger.
I cannot trigger the issue with Deskop Safari Technology Preview Release 137 (Safari 15.4, WebKit 16613.1.11.8) on Big Sur.
ewak commentedon Jan 13, 2022
I will try to install some previous Safari Technology Previews to see if I can trigger it in an intermediate version.
Using
https://web.archive.org/web/*/https://secure-appldnld.apple.com/STP/*
Here is a link to a Nov 26, 2021
https://web.archive.org/web/20211126032655if_/https://secure-appldnld.apple.com/STP/001-62651-20201022-6cd92e18-bfbe-48cc-9385-d84da8f3c24c/SafariTechnologyPreview.dmg
Also now installing IOS 15.3 Public Beta 2 which was just released.
ewak commentedon Jan 13, 2022
The link above is to a old version Release 115 (Safari 14.1, WebKit 15611.1.3.5) compatible with 10.15, So useless for this purpose.
https://web.archive.org/web/20211126032655if_/https://secure-appldnld.apple.com/STP/001-62651-20201022-6cd92e18-bfbe-48cc-9385-d84da8f3c24c/SafariTechnologyPreview.dmg
10 remaining items
not-implemented commentedon Jan 29, 2022
Great! Thanks :-)
ewak commentedon Feb 2, 2022
Hi All,
Good news... maybe.
Looks to me using the IOS15Compression tests #1399 that the current release of IOS 15.4(19E5209h) public beta, installed on my IPhone, now has working compression.
Note: I have of course NOT incorporated the user agent hasBrokenCompression function above into my tests.
It works(tm) for me, for both shared and dedicated compression as shown below.
Shared compression
compressionFlavour = uWS::CompressOptions(uWS::SHARED_COMPRESSOR | uWS::SHARED_DECOMPRESSOR);Dedicated Compression
compressionFlavour = uWS::CompressOptions(uWS::DEDICATED_COMPRESSOR | uWS::DEDICATED_DECOMPRESSOR);not-implemented commentedon Feb 2, 2022
Oh ... sounds good! So we can hopefully change the version-check to target just "Safari 15.0-15.3"
not-implemented commentedon Feb 2, 2022
I created a pull request, that can be merged, when we are 100% sure, that Safari 15.4 is fixed:
#1414
This is the updated version of
hasBrokenCompression():Output:
Updated Safari hack to target 15.0-15.3 (#1347) (#1414)
ewak commentedon Mar 17, 2022
Hi Alex,
I saw your message re macos 12.3.
Its on my list to risk "destroying" one of my machines with yet another fresh os upgrade to macOS 12.3
I have not done so yet.
I can confirm that the official IOS 15.4 is working for my tests.
gaby23s commentedon Mar 17, 2022
Hi,
I still have the issue with an iPhone 8 and iOS 15.4 !
When I return after safari has been in the background for a while, the websocket is closed.
not-implemented commentedon Mar 17, 2022
@gaby23s are you sure, you are testing this very specific issue with Safari compression? This issue has nothing to do with putting Safari into the background ... in this scenario, Safari disconnects immediately after connect.
gaby23s commentedon Mar 18, 2022
Perhaps is not exactly the same issue.
But it seem linked: https://bugs.webkit.org/show_bug.cgi?id=228296
ewak commentedon Mar 18, 2022
@gaby23s We have also experienced MANY problems dealing with network re-connection after application sleep.
We fight it with a patched version of https://github.com/pladaria/reconnecting-websocket v4.2.0. (Here is our patched version - https://github.com/ewak/reconnecting-websocket/tree/feature/ewak/improvements) which I need to PR.
We use react, setup the websocket to start closed and attach an onerror() function. In the App componentDidMount() we reattach the onmessage(), onopen(), onclose() first and THEN call reconnect(). With IOS 15.4 this has again been working fine for us.
We also use React Native WebView(effectively safari) and have had many attempts to try to use native network information to know when it is worth attempting to reconnect, none of that got us anywhere reliably, and I suspect that privacy/anit-tracking improvements in IOS further limit the effectiveness of that.
Full disclosure - Be sure that you are really reconnecting to the correct place!!!
We had a long time nasty race condition due the accidental use of default connection parameters. We were failing to "await" the download and use runtime of configuration of the websocket connection details. Depending on the users phone, state of their browser cache, network latency the reconnection would race between the default configuration connecting to development servers(firewalled) rather than the runtime downloaded configuration environment(test, qa, prod).