Skip to content

ProxyAgent loop on SocketError #3897

Description

@DTrombett

Bug Description

When the connection to the proxy server is closed with UND_ERR_SOCKET using ProxyAgent an infinite loop is created.
This is actually a double bug; I found this while investigating why ProxyAgent doesn't work with a basic http proxy built with node:http:

import { ok } from "node:assert";
import { createServer, request as httpRequest } from "node:http";
import { request as httpsRequest } from "node:https";
import { Client, ProxyAgent, request } from "undici";

const server = createServer((req, res) => {
    ok(req.url);
    const targetUrl = new URL(req.url);
    req.pipe(
        (targetUrl.protocol === "https:" ? httpsRequest : httpRequest)(
            {
                method: req.method,
                hostname: targetUrl.hostname,
                port: targetUrl.port,
                path: targetUrl.pathname + targetUrl.search,
                headers: req.headers,
            },
            (proxiedRes) => {
                ok(proxiedRes.statusCode);
                res.writeHead(
                    proxiedRes.statusCode,
                    proxiedRes.statusMessage,
                    proxiedRes.headers,
                );
                proxiedRes.pipe(res);
            },
        ).once("error", res.destroy.bind(res)),
    );
}).listen();
const address = server.address();
ok(address && typeof address === "object");

// This works fine:
console.log(
    await new Client(`http://localhost:${address.port}`)
        .request({
            method: "GET",
            path: "http://example.com",
            headers: { host: "example.com" },
        })
        .then((res) => res.body.text()),
);

// This doesn't work and creates an infinite loop trying to connect to proxy url
// For instance, listening to server.on("connection", console.log) logs infinite times
console.log(
    await request("http://example.com", {
        dispatcher: new ProxyAgent(`http://localhost:${address.port}`),
    }).then((res) => res.body.text()),
);

Now here there are two questions:

  1. Why is the connection closing in the first place? Spent hours debugging but honestly couldn't find the reason
  2. Why is it looping until a connection is established? This creates a significant overhead both on the client and on the proxy server that is flooded by requests

The error is caught here where there is already something about "avoiding a loop in client.js#connect"

} catch (err) {
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
// Throw a custom error to avoid loop in client.js#connect
callback(new SecureProxyConnectionError(err))
} else {
callback(err)
}
}

Reproducible By

Example above

Expected Behavior

If the connection is closed, the error should be thrown immediately instead of trying infinite times.
Also, I think the example above should be successful considering that using a Client works

Logs & Screenshots

The error (added throw err to avoid loop)

SocketError: other side closed
    at Socket.onHttpSocketEnd (D:\-\node_modules\undici\lib\dispatcher\client-h1.js:902:22)
    at Socket.emit (node:events:530:35)
    at endReadableNT (node:internal/streams/readable:1698:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  code: 'UND_ERR_SOCKET',
  socket: {
    localAddress: '::1',
    localPort: 60534,
    remoteAddress: '::1',
    remotePort: 60533,
    remoteFamily: 'IPv6',
    timeout: undefined,
    bytesWritten: 73,
    bytesRead: 0
  }
}

Environment

Node v22, all undici versions with ProxyAgent

Additional context

Using ProxyAgent with a "real" proxy works (even if it seems a bit slow?), so I'm not sure what is it expecting from my custom server that is different from a normal proxy

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions