CodeSampleX

Пример

undici 8.10.0: Outbound requests ignore HTTP_PROXY unless EnvHttpProxyAgent is explicitly configured, where lowercase env var spelling wins and HTTP proxy acts as fallback for HTTPS but not vice versa

Проверенный пример — npm undici 8.10.0: Outbound requests ignore HTTP_PROXY unless EnvHttpProxyAgent is explicitly configured, where lowercase env var…

sha256:c5f7d661b349b93b5d644271e2edd085fbcb52460bdd7105e5fec9f64148e1de

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
3
Ключи подписи, собравшие его
3
Заявленная среда node linux x64 node node npm

Среды запусков проверки

Окружение Контракт Этапы Запуск
node 22 · linux alpine/x64 · docker ed25519:ac1544ece1e22594 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-14
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

Кейс

HOW
Цель
Outbound requests ignore HTTP_PROXY unless EnvHttpProxyAgent is explicitly configured, where lowercase env var spelling wins and HTTP proxy acts as fallback for HTTPS but not vice versa
Пакеты
Символы
  • undici.EnvHttpProxyAgent
  • undici.ProxyAgent
  • undici.Agent
  • undici.getGlobalDispatcher
  • undici.setGlobalDispatcher
Окружение
node
Создан
2026-08-14T22:45:01Z

Контракт

  1. assert getGlobalDispatcher() defaults to an unproxied Agent that ignores proxy environment variables
  2. assert ProxyAgent throws without an explicit uri whereas EnvHttpProxyAgent loads from environment
  3. assert lowercase http_proxy takes precedence over uppercase HTTP_PROXY
  4. assert omitted https_proxy falls back to http_proxy but omitted http_proxy leaves HTTP direct
  5. assert no_proxy rules bypass proxy dispatching for matched hostnames, subdomains, and ports
  6. assert process.env.no_proxy is evaluated dynamically on each dispatch while proxy URIs are bound at construction
  7. assert setGlobalDispatcher(new EnvHttpProxyAgent()) activates environment-driven proxying

Файлы

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

NOTES.md
A model would expect default undici requests to automatically honour `process.env.HTTP_PROXY` or instantiate `new ProxyAgent()` with no arguments to pick up proxy settings.
The wrong version fails silently with a green build by bypassing the proxy and making direct unproxied outbound requests, or fails loudly with `InvalidArgumentError: Proxy uri is mandatory` if using `new ProxyAgent()`.
csx.json
{"case":{"caseId":"case:sha256:99c5c121016a6b47e1b4d36c7080f1581322e13cd5ba731a4de0a63790c653e3","constraints":{"runtime":"node"},"contract":["assert getGlobalDispatcher() defaults to an unproxied Agent that ignores proxy environment variables","assert ProxyAgent throws without an explicit uri whereas EnvHttpProxyAgent loads from environment","assert lowercase http_proxy takes precedence over uppercase HTTP_PROXY","assert omitted https_proxy falls back to http_proxy but omitted http_proxy leaves HTTP direct","assert no_proxy rules bypass proxy dispatching for matched hostnames, subdomains, and ports","assert process.env.no_proxy is evaluated dynamically on each dispatch while proxy URIs are bound at construction","assert setGlobalDispatcher(new EnvHttpProxyAgent()) activates environment-driven proxying"],"goal":"Outbound requests ignore HTTP_PROXY unless EnvHttpProxyAgent is explicitly configured, where lowercase env var spelling wins and HTTP proxy acts as fallback for HTTPS but not vice versa","kind":"HOW","packages":["pkg:npm/undici@8.10.0"],"schemaVersion":1,"symbols":["undici.EnvHttpProxyAgent","undici.ProxyAgent","undici.Agent","undici.getGlobalDispatcher","undici.setGlobalDispatcher"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/undici@8.10.0"],"schemaVersion":1,"symbols":["undici.EnvHttpProxyAgent","undici.ProxyAgent","undici.Agent","undici.getGlobalDispatcher","undici.setGlobalDispatcher"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-undici-proxy",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-undici-proxy",
      "version": "1.0.0",
      "dependencies": {
        "undici": "8.10.0"
      }
    },
    "node_modules/undici": {
      "version": "8.10.0",
      "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
      "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==",
      "license": "MIT",
      "engines": {
        "node": ">=22.19.0"
      }
    }
  }
}
package.json
{
  "name": "npm-undici-proxy",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "undici": "8.10.0"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import { Agent, ProxyAgent, EnvHttpProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici';

// Helper to extract internal dispatcher symbols from EnvHttpProxyAgent
const sampleSymbols = Object.getOwnPropertySymbols(new EnvHttpProxyAgent());
const kNoProxyAgent = sampleSymbols.find((s) => s.description === 'no proxy agent');
const kHttpProxyAgent = sampleSymbols.find((s) => s.description === 'http proxy agent');
const kHttpsProxyAgent = sampleSymbols.find((s) => s.description === 'https proxy agent');

assert.ok(kNoProxyAgent && kHttpProxyAgent && kHttpsProxyAgent, 'Dispatcher symbols must exist');

function getProxyUri(proxyAgentInstance) {
  if (!proxyAgentInstance) return null;
  const syms = Object.getOwnPropertySymbols(proxyAgentInstance);
  const kProxy = syms.find((s) => s.description === 'proxy agent options');
  return kProxy ? proxyAgentInstance[kProxy]?.uri : null;
}

// Trap 1: Default global dispatcher does NOT honour proxy environment variables
const defaultDispatcher = getGlobalDispatcher();
assert.equal(defaultDispatcher.constructor.name, 'Agent', 'Default dispatcher must be direct Agent');
assert.notEqual(defaultDispatcher.constructor.name, 'EnvHttpProxyAgent', 'Default dispatcher is not EnvHttpProxyAgent');

// Trap 2: ProxyAgent vs EnvHttpProxyAgent constructor contract
assert.throws(() => new ProxyAgent(), { message: 'Proxy uri is mandatory' });
assert.doesNotThrow(() => new EnvHttpProxyAgent());

// Trap 3: Option precedence over environment variables
{
  const agent = new EnvHttpProxyAgent({
    httpProxy: 'http://opt-proxy.example:8080',
    httpsProxy: 'http://opt-https-proxy.example:8443'
  });
  assert.equal(getProxyUri(agent[kHttpProxyAgent]), 'http://opt-proxy.example:8080/');
  assert.equal(getProxyUri(agent[kHttpsProxyAgent]), 'http://opt-https-proxy.example:8443/');
}

// Trap 4: Asymmetric proxy fallback for HTTP vs HTTPS
{
  // Only HTTP proxy configured -> HTTPS falls back to HTTP proxy
  const httpOnly = new EnvHttpProxyAgent({ httpProxy: 'http://http-proxy.example:8080' });
  assert.notEqual(httpOnly[kHttpProxyAgent], httpOnly[kNoProxyAgent]);
  assert.equal(httpOnly[kHttpsProxyAgent], httpOnly[kHttpProxyAgent]);

  // Only HTTPS proxy configured -> HTTP stays unproxied direct Agent
  const httpsOnly = new EnvHttpProxyAgent({ httpsProxy: 'http://https-proxy.example:8443' });
  assert.equal(httpsOnly[kHttpProxyAgent], httpsOnly[kNoProxyAgent]);
  assert.notEqual(httpsOnly[kHttpsProxyAgent], httpsOnly[kNoProxyAgent]);
}

// Trap 5: Dispatch routing and NO_PROXY matching rules (no network)
{
  const agent = new EnvHttpProxyAgent({
    httpProxy: 'http://http-gw.example:8080',
    httpsProxy: 'http://https-gw.example:8443',
    noProxy: 'bypass.example,*.sub.example,host-with-port.example:9000'
  });

  let target = null;
  agent[kNoProxyAgent].dispatch = () => { target = 'direct'; return true; };
  agent[kHttpProxyAgent].dispatch = () => { target = 'http-proxy'; return true; };
  agent[kHttpsProxyAgent].dispatch = () => { target = 'https-proxy'; return true; };

  agent.dispatch({ origin: 'http://api.example.com', path: '/' }, {});
  assert.equal(target, 'http-proxy', 'HTTP requests route to http proxy');

  agent.dispatch({ origin: 'https://api.example.com', path: '/' }, {});
  assert.equal(target, 'https-proxy', 'HTTPS requests route to https proxy');

  agent.dispatch({ origin: 'http://bypass.example', path: '/' }, {});
  assert.equal(target, 'direct', 'NO_PROXY exact host routes direct');

  agent.dispatch({ origin: 'https://service.sub.example', path: '/' }, {});
  assert.equal(target, 'direct', 'NO_PROXY wildcard domain routes direct');

  agent.dispatch({ origin: 'http://host-with-port.example:9000', path: '/' }, {});
  assert.equal(target, 'direct', 'NO_PROXY matching port routes direct');

  agent.dispatch({ origin: 'http://host-with-port.example:8080', path: '/' }, {});
  assert.equal(target, 'http-proxy', 'NO_PROXY mismatched port routes to proxy');
}

// Trap 6: Dynamic NO_PROXY evaluation vs Static HTTP_PROXY binding
{
  const origHttp = process.env.http_proxy;
  const origNo = process.env.no_proxy;
  const origHttpUpper = process.env.HTTP_PROXY;
  const origNoUpper = process.env.NO_PROXY;

  try {
    delete process.env.no_proxy;
    delete process.env.NO_PROXY;
    process.env.http_proxy = 'http://init-proxy.example:8080';

    const dynAgent = new EnvHttpProxyAgent();
    let target = null;
    dynAgent[kNoProxyAgent].dispatch = () => { target = 'direct'; return true; };
    dynAgent[kHttpProxyAgent].dispatch = () => { target = 'proxy'; return true; };

    dynAgent.dispatch({ origin: 'http://dyn.example', path: '/' }, {});
    assert.equal(target, 'proxy', 'Initial dispatch uses proxy');

    process.env.no_proxy = 'dyn.example';
    dynAgent.dispatch({ origin: 'http://dyn.example', path: '/' }, {});
    assert.equal(target, 'direct', 'Mutating process.env.no_proxy updates dispatch dynamically');

    process.env.http_proxy = 'http://changed-proxy.example:9999';
    assert.equal(getProxyUri(dynAgent[kHttpProxyAgent]), 'http://init-proxy.example:8080/');
  } finally {
    if (origHttp !== undefined) process.env.http_proxy = origHttp; else delete process.env.http_proxy;
    if (origNo !== undefined) process.env.no_proxy = origNo; else delete process.env.no_proxy;
    if (origHttpUpper !== undefined) process.env.HTTP_PROXY = origHttpUpper; else delete process.env.HTTP_PROXY;
    if (origNoUpper !== undefined) process.env.NO_PROXY = origNoUpper; else delete process.env.NO_PROXY;
  }
}

// Trap 7: setGlobalDispatcher activation contract
{
  const customAgent = new EnvHttpProxyAgent({ httpProxy: 'http://proxy.example:8080' });
  setGlobalDispatcher(customAgent);
  assert.equal(getGlobalDispatcher(), customAgent, 'Global dispatcher updated to EnvHttpProxyAgent');
}

Исходный сидер

anonymous