nodejs / undici

An HTTP/1.1 client, written from scratch for Node.js
https://nodejs.github.io/undici
MIT License
6.24k stars 543 forks source link

`fetch` throws UND_ERR_SOCKET error #1412

Closed regseb closed 1 year ago

regseb commented 2 years ago

Bug Description

A request on this page of the Daily Mail throws an _UND_ERRSOCKET error. There is no problem with https.

Reproducible By

Execute this script:

import https from "node:https";
import zlib from "node:zlib";

const testWithHttps = (url) => {
    return new Promise((resolve, reject) => {
        const req = https.request(new URL(url), (res) => {
            const gunzip = zlib.createGunzip();
            res.pipe(gunzip);

            const buffer = [];
            gunzip.on("data", (chunk) => buffer.push(chunk))
                  .on("error", (err) => reject(err))
                  .on("end", () => resolve(buffer.join("")));
        });
        req.on("error", (err) => reject(err));
        req.setHeader("Accept", "*/*");
        req.setHeader("Accept-Encoding", "br, gzip, deflate");
        req.setHeader("Accept-Language", "*");
        req.setHeader("Sec-Fetch-Mode", "cors");
        req.setHeader("User-Agent", "undici");
        req.end();
    });
};

const testWithFetch = async (url) => {
    const response = await fetch(url);
    return response.text();
};

console.log("PTS (https://ptsv2.com/t/undici)");
let url = "https://ptsv2.com/t/undici/post";
console.log("HTTPS: " + (await testWithHttps(url)).substring(0, 100));
console.log("FETCH: " + (await testWithFetch(url)).substring(0, 100));

console.log("\nDaily Mail");
url = "https://www.dailymail.co.uk/sciencetech/article-8057229" +
                                  "/Scientists-create-stunning-gifs-Mars-sand" +
                                "-dunes-understand-conditions-impact-them.html";
console.log("HTTPS: " + (await testWithHttps(url)).substring(0, 100));
console.log("FETCH: " + (await testWithFetch(url)).substring(0, 100));

Expected Behavior

PTS (https://ptsv2.com/t/undici)
HTTPS: Thank you for this dump. I hope you have a lovely day!
(node:22125) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
FETCH: Thank you for this dump. I hope you have a lovely day!

Daily Mail
HTTPS: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-tr
FETCH: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-tr

Logs & Screenshots

PTS (https://ptsv2.com/t/undici)
HTTPS: Thank you for this dump. I hope you have a lovely day!
(node:22125) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
FETCH: Thank you for this dump. I hope you have a lovely day!

Daily Mail
HTTPS: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-tr
node:internal/deps/undici/undici:6266
            fetchParams.controller.controller.error(new TypeError("terminated", {
                                                    ^

TypeError: terminated
    at Fetch.onAborted (node:internal/deps/undici/undici:6266:53)
    at Fetch.emit (node:events:527:28)
    at Fetch.terminate (node:internal/deps/undici/undici:5522:14)
    at Object.onError (node:internal/deps/undici/undici:6357:36)
    at Request.onError (node:internal/deps/undici/undici:2023:31)
    at errorRequest (node:internal/deps/undici/undici:3949:17)
    at TLSSocket.onSocketClose (node:internal/deps/undici/undici:3411:9)
    at TLSSocket.emit (node:events:539:35)
    at node:net:715:12
    at TCP.done (node:_tls_wrap:581:7) {
  [cause]: SocketError: closed
      at TLSSocket.onSocketClose (node:internal/deps/undici/undici:3399:35)
      at TLSSocket.emit (node:events:539:35)
      at node:net:715:12
      at TCP.done (node:_tls_wrap:581:7) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: undefined,
      localPort: undefined,
      remoteAddress: undefined,
      remotePort: undefined,
      remoteFamily: 'IPvundefined',
      timeout: undefined,
      bytesWritten: 294,
      bytesRead: 83018
    }
  }
}

Node.js v18.1.0

Environment

Additional context

In test case, I modified the https headers to have the same values as fetch. You can see the requests (with their headers) on this page of PTS.

mcollina commented 2 years ago

Could you reproduce with a self-hosted server?

regseb commented 2 years ago

@mcollina I reproduce the same error with a secure HTTP2 server.

// node server.js
import fs from "node:fs/promises";
import http2 from "node:http2";

// openssl genrsa -out key.pem
// openssl req -new -key key.pem -out csr.pem
// openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
// rm csr.pem
const server = http2.createSecureServer({
    key:  await fs.readFile("key.pem"),
    cert: await fs.readFile("cert.pem"),
});
server.on("error", (err) => console.log(err));
server.on("stream", (stream) => stream.end("foo"));
server.listen(11011);
// NODE_TLS_REJECT_UNAUTHORIZED='0' node client.js
import http2 from "node:http2";

const testWithHttp2 = (url) => {
    return new Promise((resolve, reject) => {
        const client = http2.connect(url);
        const req = client.request();
        const buffer = [];
        req.on("data", (chunk) => buffer.push(chunk));
        req.on("error", (err) => reject(err));
        req.on("end", () => {
            resolve(buffer.join(""));
            client.close();
        });
        req.end();
    });
};

const testWithFetch = async (url) => {
    const response = await fetch(url);
    return response.text();
};

let url = "https://localhost:11011/";
console.log("HTTP2: " + (await testWithHttp2(url)));
console.log("FETCH: " + (await testWithFetch(url)));
(node:23659) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.
(Use `node --trace-warnings ...` to show where the warning was created)
HTTP2: foo
(node:23659) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
node:internal/deps/undici/undici:6266
            fetchParams.controller.controller.error(new TypeError("terminated", {
                                                    ^

TypeError: terminated
    at Fetch.onAborted (node:internal/deps/undici/undici:6266:53)
    at Fetch.emit (node:events:527:28)
    at Fetch.terminate (node:internal/deps/undici/undici:5522:14)
    at Object.onError (node:internal/deps/undici/undici:6357:36)
    at Request.onError (node:internal/deps/undici/undici:2023:31)
    at errorRequest (node:internal/deps/undici/undici:3949:17)
    at TLSSocket.onSocketClose (node:internal/deps/undici/undici:3411:9)
    at TLSSocket.emit (node:events:539:35)
    at node:net:715:12
    at TCP.done (node:_tls_wrap:581:7) {
  [cause]: SocketError: closed
      at TLSSocket.onSocketClose (node:internal/deps/undici/undici:3399:35)
      at TLSSocket.emit (node:events:539:35)
      at node:net:715:12
      at TCP.done (node:_tls_wrap:581:7) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: undefined,
      localPort: undefined,
      remoteAddress: undefined,
      remotePort: undefined,
      remoteFamily: 'IPvundefined',
      timeout: undefined,
      bytesWritten: 176,
      bytesRead: 239
    }
  }
}

Node.js v18.1.0

With a unsecure HTTP2 server, fetch throws a _HPE_INVALIDCONSTANT error:

node:internal/deps/undici/undici:5575
          p.reject(Object.assign(new TypeError("fetch failed"), { cause: response.error }));
                                 ^

TypeError: fetch failed
    at Object.processResponse (node:internal/deps/undici/undici:5575:34)
    at node:internal/deps/undici/undici:5901:42
    at node:internal/process/task_queues:140:7
    at AsyncResource.runInAsyncScope (node:async_hooks:202:9)
    at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8) {
  cause: HTTPParserError: Expected HTTP/
      at Parser.execute (node:internal/deps/undici/undici:3075:19)
      at Parser.readMore (node:internal/deps/undici/undici:3034:16)
      at Socket.onSocketReadable (node:internal/deps/undici/undici:3364:14)
      at Socket.emit (node:events:527:28)
      at emitReadable_ (node:internal/streams/readable:590:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:81:21) {
    code: 'HPE_INVALID_CONSTANT',
    data: '\x00\x00\x00\x04\x00\x00\x00\x00\x00'
  }
}

Node.js v18.1.0

There is issues and pull request to add support for HTTP/2:

ronag commented 2 years ago

undici doesn't support http2 yet

FourCinnamon0 commented 1 year ago

I have the same issue with this url

mcollina commented 1 year ago

Closing as a duplicate of https://github.com/nodejs/undici/issues/902. fetch is missing HTTP/2 support.

iambumblehead commented 2 months ago

I encountered this many times earlier today using a flakey network with node v20.15.1. The network conditions here have improved so that I'm no longer seeing the issue.

No problem for me, this is just a friendly comment sharing my experience that an issue still exists at node v20