Перейти к основному содержимому

Генератор http

Когда применять — когда значение должно прийти из логики, которой у TDC нет: настоящий алгоритм контрольной цифры, который вы уже написали, обращение к своей базе, любой расчёт, который тяжело выразить в конфиге. Вы поднимаете небольшой сервис, а TDC к нему обращается: движок становится клиентом вашего сервиса, а не хостом вашего кода. Это и есть точка расширения — всё, что вы спрячете за HTTP-адрес, становится частью ваших данных.

Два режима: он генерирует или он обрабатывает

Какой из них — решает один атрибут, и это по-настоящему разные работы:

inчто получает ваш сервисчто он делает
Источникнетничего, кроме счётчикапридумывает значения сам — работает как генератор
Обработчикестьваши значения, по одному на строкупреобразует присланное и возвращает обратно

Оба сразу, к одному и тому же сервису — первую колонку отдают и получают изменённой, вторая берётся из ниоткуда:

<sequence name="City">
<gen type="text" value="Paris,Berlin,Tokyo" order="sequential"/>
</sequence>

<sequence name="Handled">
<gen type="http" src="http://127.0.0.1:5599/gen" in="City"/> <!-- обработчик -->
</sequence>

<sequence name="Made">
<gen type="http" src="http://127.0.0.1:5599/gen"/> <!-- источник -->
</sequence>
./run modes.tdc
Paris  ->  [Paris ok]    |  Made: SRC-000
Berlin ->  [Berlin ok]   |  Made: SRC-001
Tokyo  ->  [Tokyo ok]    |  Made: SRC-002

Режим обработчика — более полезный из двух, и его легче всего не заметить: он позволяет сервису, который у вас уже есть, доделать значение, начатое TDC, — проверить, добавить контрольную цифру, найти по справочнику, перевести, — вместо того чтобы заменять генератор целиком.

Ваш сервис отличает режимы по телу запроса: пустое тело — это режим источника, а заголовок X-TDC-Count говорит, сколько значений придумать.

Как написать сам сервис

Полный рабочий сервис — на Node, Python и Java, с обоими режимами — и как сделать его воспроизводимым от сида, вынесены на отдельную страницу: Как написать сервис-генератор.

Колонка входных значений уходит одним запросом; ответ приходит по значению на строку, в том же порядке. Здесь сервис приводит каждое значение к верхнему регистру (a → A).
  • Aколонка входа — значения, которые дала ваша последовательность
  • Bваш сервис: TDC только обращается к нему, но не запускает
  • Cответ — одно значение на строку, в порядке отправки

Атрибуты

АтрибутЧто задаёт
srcадрес сервиса — http://127.0.0.1:5566/gen (локально, быстро) или внешний хост. https тоже работает
inпоследовательность, чьё значение уходит на каждой строке, — именно это делает сервис обработчиком. Без него сервис — источник: ничего не получает и придумывает каждое значение
on_errorfail (по умолчанию) — остановиться с внятным сообщением; empty — оставить ячейку пустой и продолжить
timeoutсколько секунд ждать один ответ, прежде чем сдаться. По умолчанию 30

in называет более раннюю последовательность — уходит то значение, что она дала на каждой строке.

Контракт, который реализует ваш сервис

Движок говорит по одному маленькому протоколу и ничего сверх него не ждёт:

  • POST на src, с заголовком X-TDC-Count: N — сколько значений нужно.
  • Тело — это N входных значений, по одному на строку, в порядке строк. Без in тело пустое, а N берётся из заголовка.
  • Ответ должен быть ровно N строк, в том же порядке — строка i отвечает на вход i. Обычный текст.

Всё структурное — забота вашего сервиса. Если внутри он работает с JSON, наружу отдаёт то одно поле, что вам нужно, текстом — TDC внутри держит всё строками.

Один запрос на колонку, а не на строку. Движок шлёт всю пачку разом, поэтому тысяча строк — это один запрос. Сервис, написанный «строка на входе — строка на выходе», тоже работает: он просто идёт циклом по строкам одного полученного запроса.

Почему тысяча строк — это один запрос. Построчная форма (слева) была бы тысячей обращений; TDC шлёт всю колонку одним (справа).
  • Aпо запросу на строку — так TDC НЕ делает; это был бы вызов на каждое значение
  • Bодин запрос на всю колонку — вся пачка, один обход по сети

Именно это держит скорость: цена — один обход по сети плюс работа самого сервиса, а не обход на каждое значение. Поэтому же http работает на in-memory движке (одна из пяти форм, которые так делают), и его лучше держать для сервиса на вашей машине или для прогона, размер которого вы прикинули нарочно, — а не для миллиарда строк к далёкому адресу.

Целый сервис на пяти языках

Каждый из них полон и отвечает на оба режима: оборачивает то, что вы прислали, и придумывает значения, когда тело пустое. Выбирайте язык — ведут себя одинаково.

import { createServer } from 'node:http';

createServer((req, res) => {
const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {
const count = Number(req.headers['x-tdc-count'] ?? '0');
const sent = Buffer.concat(chunks).toString('utf8');

const out =
sent === ''
? Array.from({ length: count }, (_, i) => 'SRC-' + String(i).padStart(3, '0')) // source
: sent.split('\n').map((line) => '[' + line + ' ok]'); // handler

const body = out.join('\n');
res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': Buffer.byteLength(body) });
res.end(body);
});
}).listen(5801, '127.0.0.1');

Запустите любой, направьте src на его порт и прогоните конфиг из раздела Два режима — все пять дадут один и тот же вывод.

Прочитайте это, прежде чем писать свой, — это не опционально

Сервисы выше — самое короткое, что работает. Они невоспроизводимы: прогоните конфиг дважды, и придуманные значения будут какими вздумается сервису.

→ Как написать сервис-генератор — вот страница, которая важна. Там, с рабочим кодом на всех языках:

  • как сделать прогон воспроизводимым через X-TDC-Seed, который шлёт TDC, — то единственное, что возвращает этому генератору его гарантию;
  • почему значение(сид, i), а не next() — сервис не может обещать порядок вызовов, и итератор тихо ломается при повторах и одновременных запросах;
  • ловушка 32 бит, из-за которой наивный перенос на Python молча расходится с Node и Java;
  • список перед стартом: точное число строк, порядок, переносы, одновременность, пачка.

Пропустите — и данные будут выглядеть нормально, но не будут воспроизводимыми. Это самый дорогой вид «неправильно».

Когда что-то ломается

Сервис вне контроля TDC, поэтому отказы обрабатываются, а не прячутся:

  • on_error="fail" (по умолчанию) останавливает прогон с сообщением, называющим последовательность и сервис — http service for sequence "Checked" at … returned 500. Пустая колонка в готовом файле — сюрприз хуже, чем внятная остановка.
  • on_error="empty" оставляет затронутую колонку пустой и доводит прогон до конца — для случая, когда нужен вывод «как получится». Дырки проверяете вы.
  • 429 (слишком много запросов) всегда останавливает, даже при empty. «Притормози» и «отдать целую колонку потоком» несовместимы, а продолжение тихо обрезало бы данные.
  • Сервис, который не отвечает, отсекается по timeout, а не вешает прогон.
  • Сервис, который заливает — отвечает сильно больше, чем по значению на строку, — обрезается на 64 МБ с ошибкой, а не читается в память до конца.

Чего он не обещает

Это единственный генератор, который жертвует гарантиями, которые остальной TDC держит. Проговорите их себе, прежде чем тянуться к нему:

  • Невоспроизводимо. Значения решает сервис, поэтому seed ничего не гарантирует, и повторный прогон даёт другие данные. Конфиг с http никогда не считается воспроизводимым.
  • Порядок следует сервису, а не сиду.
  • Локально или скромные объёмы. Через интернет большой прогон — это большое число обращений наружу; это для сервиса на вашей машине или для прогона, размер которого вы прикинули нарочно. Не для миллиарда строк к публичному адресу.

См. также