nodejs / nodejs/undici

feature: Add low-level API for capturing and managing sessions/clients in Agent

Open
#4,287 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
JavaScript
Stars
7.7k
Forks
880
Avg merge
2d 16h
Merged PRs (30d)
68

Description

I am faced with such a problem that I need to close the connection and the client itself according to the condition

How do I get a connection (Client) before it becomes available and the next request goes through it?

My Agent keeps a pool of connections through different proxy servers, and I need to close clients that don't suit me for any reason.

How can I do this without completely rewriting the clients and agent? It turns out that the response has no access to either the client or the connection.

Maybe make an additional implementation with acquire/release clients or proxy wrappers over clients of an existing agent.

Add low-level API for capturing and managing sessions/clients in Dispatcher

Problem Description

Currently, undici does not provide a convenient way to:

  1. Capture an individual HTTP client (or “session”) from the connection pool (Dispatcher) to perform a series of requests.
  2. Close or release specific connections based on response outcome (e.g., on a 403), without affecting the rest of the pool.
  3. Fine-tune the lifecycle of connections: controlling counts, re-opening, and manual shutdown.

The existing Agent/Dispatcher API only offers a global pool (connections), but lacks something like a session() or acquire() method to carve out its own sub-pool of connections that you can explicitly close or return.


Expected Behavior

  • Method dispatcher.session(options?: { connections?: number; }): Dispatcher & SessionControls

    • Returns a Dispatcher-compatible object (a Session) that:
      • Implements the same interface as Dispatcher (so you can pass it anywhere a Dispatcher is expected).
      • Owns its own subset of TCP connections (optionally limited by connections).
      • Exposes additional methods:
        • close(): Promise<void> — force-close all connections in this session.
        • release(): Promise<void> — gracefully return all open connections back to the parent pool.
  • Customizable at creation: allow passing TLS settings, timeouts, proxy options, etc.

  • Backward compatibility: if session() isn’t called, Dispatcher continues working as before.


Usage Example

import { buildHttpProxyConnector } from '@undicijs/proxy';
import { Agent, request, type Dispatcher } from 'undici';

const connector = buildHttpProxyConnector('http://0.0.0.0:8001/', {
  requestTls: { rejectUnauthorized: false },
});

const dispatcher = new Agent({
  connect: (opts, cb) => connector(opts, cb),
  connections: 50,
});

async function callApiWithSession(dispatcher: Dispatcher) {
  // Acquire a session (which is itself a Dispatcher) with two dedicated connections
  const session = dispatcher.session({ connections: 2 });

  try {
    const resp = await session.request('https://api.ipify.org');
    if (resp.statusCode === 403) {
      // Force-close all connections in this session
      await session.close();
      return null;
    }
    return await resp.body.text();
  } finally {
    // On success or after errors (if not closed), return connections to the pool
    await session.release();
  }
}

async function main() {
  const ip = await callApiWithSession(dispatcher);
  console.log(ip);
}

main();

Motivation

  1. Error handling per-session.
    On a 403 or 5xx error, you can tear down only the affected connections instead of the entire Agent.

  2. Batching requests.
    Some workflows need to group multiple requests under a controlled max-connection session.

  3. Flexible optimization.
    Create multiple sessions with different configs (TLS, timeouts, proxies) without instantiating entirely new Agent objects.


Proposed API Sketch

interface Dispatcher {
  /**
   * Create a new session that uses a subset of this Dispatcher’s pool.
   * The returned object implements the Dispatcher interface.
   */
  session(options?: {
    connections?: number;
    // ...other config
  }): Dispatcher & SessionControls;
}

/** Extra control methods on a Session */
interface SessionControls {
  /** Force-close all TCP connections in this session. */
  close(): Promise<void>;
  /** Gracefully return open connections to the parent pool. */
  release(): Promise<void>;
}

Alternatives & Current Workarounds

  • New Agent(...) per use case — heavyweight, as each Agent creates its own pool.
  • Internal hacks — manually closing Client sockets via resp.body.socket.close() or util.hijack(), which is unstable and unsupported.

An official low-level session API will simplify consumer code, make behavior predictable, and eliminate these fragile workarounds. Please consider adding this in the next major release!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue names no specific files or tests; start by reading the existing Dispatcher and Agent APIs in undici. Assess how a Dispatcher-compatible session could own connections and expose close() and release() without changing existing behavior. Done means the proposed lifecycle and configuration behavior are implemented with coverage for session creation, forced closure, graceful release, and backward compatibility.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, typescript
Domain
networking
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.