# Undici: HTTP-клиент в Node.js

Девять практических сценариев, 23 записанных настоящих выполнения, собственный локальный upstream и небольшой gateway. Статья и демо: ae-nekrasov.ru.

## Что исполняется

`npm test` и `npm run capture` действительно запускают Undici и HTTP-сервер на свободном порту loopback. MockAgent — отдельный сценарий, в котором реальная сеть специально запрещена. HTML-мастерская воспроизводит JSON этих запусков: Node.js не запускается внутри браузера. Изменение темпа просмотра не меняет измеренное время.

`public/capture.json` содержит версии, платформу, дату, SHA256 исходников и lock-файла. Можно самостоятельно пересоздать записи. Время зависит от компьютера и искусственных задержек, это не benchmark библиотек.

## Зависимости

Node.js >=22.19.0, рекомендуем поддерживаемую LTS-ветку 24. Проект закрепляет Undici 8.12.4 и Bootstrap 5.3.9. Встроенный `fetch` Node имеет собственную версию Undici (`process.versions.undici`). Передавая Agent из Undici 8 во встроенный fetch Node 24, пример использует `Dispatcher1Wrapper`: напрямую эти поколения dispatcher несовместимы. Для `fetch` из пакета и `request` обёртка не нужна.

## Windows / PowerShell

Распакуйте архив, перейдите в `undici-project`. Установите Node LTS с официального nodejs.org. Откройте новое окно терминала.

```powershell
node --version
npm.cmd ci
npm.cmd test
npm.cmd run capture
npm.cmd run build
npm.cmd start
```

Откройте http://127.0.0.1:8560. Остановка — Ctrl+C. Кнопка скачивания общего ZIP рассчитана на размещение на сайте; локально исходники уже находятся у вас в папке.

Реальный путь gateway → upstream, три отдельных терминала:

```powershell
# Терминал 1
npm.cmd run upstream
# Терминал 2
$env:UPSTREAM_ORIGIN = 'http://127.0.0.1:8561'
npm.cmd run gateway
# Терминал 3
curl.exe -i http://127.0.0.1:8562/api/catalog
```

После опыта остановите оба процесса Ctrl+C. При смене портов задайте `UPSTREAM_PORT`, `UPSTREAM_ORIGIN` и `PORT` согласованно. Не останавливайте сторонний процесс, если нужный порт занят.

## Linux

Установите поддерживаемый Node LTS из доверенного канала, проверьте версии. Не используйте старую версию Node из репозитория дистрибутива без проверки.

```bash
node --version
npm ci
npm test
npm run capture
npm run build
npm start
```

В разных терминалах для настоящего gateway:

```bash
npm run upstream
UPSTREAM_ORIGIN=http://127.0.0.1:8561 npm run gateway
curl -i http://127.0.0.1:8562/api/catalog
```

Для изолированной проверки на Docker можно использовать Node 24, но контейнер не доказывает настройку реального VPS, DNS и TLS. Не монтируйте Windows node_modules в Linux: делайте `npm ci` внутри новой папки или контейнера.

## Linux / VPS

`deploy/undici-gateway.service` — шаблон systemd. Создайте системного пользователя `undici-demo`, установите проект в `/opt/undici-demo` от имени оператора и выполните `npm ci --omit=dev`. Сервису достаточно чтения исходников. Скопируйте пример окружения в `/etc/undici-demo.env`, поменяйте origin на свой разрешённый API, права 0640 root:undici-demo. Не вставляйте реальные токены в Git, статью или capture.

```bash
sudo useradd --system --home /opt/undici-demo --shell /usr/sbin/nologin undici-demo
sudo install -d -o root -g undici-demo -m 0750 /opt/undici-demo
# Разместите файлы проекта в /opt/undici-demo; выполните npm ci внутри него.
sudo install -o root -g undici-demo -m 0640 deploy/undici-demo.env.example /etc/undici-demo.env
sudoedit /etc/undici-demo.env
command -v node
# Если Node не /usr/bin/node, исправьте ExecStart в unit.
sudo install -m 0644 deploy/undici-gateway.service /etc/systemd/system/undici-gateway.service
sudo systemctl daemon-reload
sudo systemctl enable --now undici-gateway
sudo systemctl status undici-gateway --no-pager
curl -i http://127.0.0.1:8562/api/catalog
sudo journalctl -u undici-gateway -n 50 --no-pager
```

Сервис слушает только 127.0.0.1. Для домена используйте существующий Caddy/Nginx; пример `deploy/Caddyfile` нужно объединить со своей конфигурацией, не затирать действующий сайт. Проверка Caddy: `sudo caddy validate --config /etc/caddy/Caddyfile`, затем `sudo systemctl reload caddy`. Публичные DNS и порты 80/443 нужны для обычной автоматической выдачи HTTPS. Тестовый upstream из архива не является production API; он нужен для локальных опытов, на VPS укажите свой API с контрактом `/catalog`.

Gateway ограничивает путь, origin, число активных запросов (16), соединения (4), дедлайн (3 с) и размер JSON (256 КиБ), проверяет минимальную форму ответа. Права пользователя, бизнес-валидация и аутентификация — часть вашего приложения. Настройте их до публикации приватных данных. Добавление query-параметра `url` и перенаправление на пользовательский адрес нарушит исходную модель безопасности.

## Структура

- `src/upstream.mjs` — вымышленные услуги, заявки, задержки и файловая выгрузка.
- `src/client.mjs` — bounded GET retry, pipeline загрузки, безопасный журнал.
- `src/scenarios.mjs` — 23 запуска, разделённые на 9 тем.
- `src/gateway.mjs` — настоящий небольшой HTTP → HTTP сервис.
- `tools/capture.mjs` — записи и хеши происхождения.
- `tools/build.mjs` — локальные CSS, исходники, лицензии.
- `tools/serve.mjs` — локальная раздача публичной мастерской.
- `tests/client.test.mjs` — реальные сценарии и граничные случаи.
- `public/` — публикуемая статическая мастерская без токенов и приватных данных.
- `deploy/` — образцы Linux/VPS.

## Ограничения и эксплуатация

Pool создаётся на жизненный цикл сервиса, а не на каждый запрос. Число запросов в очереди приложения нужно ограничивать отдельно. В демонстрации HTTP/1.1 используется локальный HTTP; сценарии не утверждают проверку HTTP/2/TLS/proxy.

Повторы GET ограничены числом попыток и AbortSignal. `Retry-After` сверх бюджета означает отказ от раннего повтора. Показан учебный upstream с малыми JSON; перед чужим API используйте ограниченный разбор тела как в gateway. POST автоматически не повторяется: сервер обязан атомарно связывать ключ идемпотентности с операцией, содержимым и областью пользователя.

Файлы пишутся в отдельную временную папку и удаляются после записи результата. Для production загрузки давайте функции `download` уникальный приватный путь, определяйте политику имени, срока хранения и свободного места. `Content-Length` не заменяет подсчёт реально прочитанных байтов.

Не полагайтесь на точность parser timeout до миллисекунды. Общий дедлайн тоже зависит от event loop: блокирующая CPU-задача может задержать таймер. Клиентский abort не гарантирует откат операции, уже начатой на сервере.

## Источники

- https://undici.nodejs.org/
- https://undici.nodejs.org/api/Pool
- https://undici.nodejs.org/api/MockAgent
- https://undici.nodejs.org/api/Client
- https://undici.nodejs.org/api/Dispatcher
- https://undici.nodejs.org/api/DiagnosticsChannel
- https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8
- https://nodejs.org/api/globals.html#fetch
- https://nodejs.org/api/stream.html

Авторский код — MIT, лицензии зависимостей — THIRD-PARTY-LICENSES.txt.
