Azure / iisnode

Hosting node.js applications in IIS on Windows
Other
667 stars 101 forks source link

Safari 10 won't load https - keeps repeating the same request and finally fails #126

Closed microspec-chris closed 2 years ago

microspec-chris commented 2 years ago

We have various services and APIs served via Node.js version 14 on IIS using iisnode and generally we've been very pleased with the experience. In the last several 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");
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()}`);
    }
}).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\6d68f67e-ce-4478e-96c4-6a9472c79c14
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 appears to respond to successfully.

Note that I can successfully visit https pages on the server that are plain .htm files, or served via ColdFusion or Classic ASP, so this is something more specific to Node.js. I can also serve the Node.js app locally on my LAN with a self-signed cert and access the page at https://192.168.0.8.

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.

This question is also posted on Stack Overflow at https://stackoverflow.com/questions/71037910/safari-10-fails-to-load-https-with-node-js-iisnode-spams-requests

microspec-chris commented 2 years ago

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: