We have various services and APIs served via Node.js version 14 on IIS using iisnode (https://github.com/Azure/iisnode). In the last couple months we've had reports from people using Safari that they can't use some of these services.
I've narrowed the problem down to https URLs - http works fine. If I enter the https URL of my test page in Safari 10 on iOS 10.3.3 the browser loading bar stays in the same place for 60 seconds then gives up with the message: "Safari could not open the page because the server stopped responding."
Here's the Node.js server code to run on the iisnode server:
const http = require("http");
const server = http.createServer(async(req, res) => {
if (req.url === "/test" && req.method === "GET") {
console.log(`Request to /test at ${Date.now()}`);
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(`/test at ${Date.now()}`);
}
});
server.listen(process.env.PORT, () => {
console.log(`server started on port: ${process.env.PORT}`);
});
Navigating Safari to /test yields the following iisnode log:
server started on port: \\.\pipe\67678fe-c6de-448e-96c4-6c79ca947214
Request to /test at 1644335072668
Request to /test at 1644335072888
Request to /test at 1644335072993
Request to /test at 1644335073079
Request to /test at 1644335073156
Request to /test at 1644335073242
...
The iisnode log shows that the server received the requests and successfully served a response. In fact, Safari repeats the request every 100 ms or so, all of which the Node.js app appear to respond to successfully.
Note that I can successfully visit https pages on the server that are served via ColdFusion or Classic ASP, so this is something more specific to Node.js.
I can duplicate this issue with both iPhone and iPad with Safari 10 on iOS 10.3.3. Unfortunately I can't test in a different browser on these devices since those apps require a higher version of iOS.
These versions of Safari work fine:
I'm guessing this has something to do with Safari (or iOS) rejecting the certificate for https but I'm not sure how to get further detail. I've tried opening the developer console on my MacBook for the problematic iPhone but it gave no errors about why the https page could not be loaded.
I solved this with the following code:
const server = http.createServer(async (req, res) => {
res.removeHeader("Connection");
From what I can tell the request forwarded from IIS to the Node.js process is over http and http/1 and so the response from Node.js includes the implicit header "Connection". IIS includes that header when sending the response back to the client, even though it does so via HTTP/2 over https. Certain versions of Safari fail when that forbidden "Connection" header is included over HTTP/2 so it continues to retry the request.
Removing this header solved the issue across the number of Node.js apps we have running through IISNode, include those using Express.js and those with stock Node.js APIs (as in the prior examples).
This issue only happened with https because https is required for HTTP/2.
Big thanks to the following posts which got me started on the solution: