redb.Route.AS2
redb.Route.AS2

Если вы поставляете товар в крупную розницу, возите грузы для 3PL-оператора, шлёте платёжные извещения банку или обмениваетесь медицинскими транзакциями X12 — вы почти наверняка обмениваетесь этими документами по AS2. Заказ (EDI 850), счёт (810), уведомление об отгрузке (856), платёжное авизо (820) уходят партнёру не почтой и не через REST, а как подписанный и зашифрованный S/MIME-конверт поверх HTTP, с подписанной квиткой-распиской в ответ. Так работает регламентированный B2B-документооборот в рознице, логистике, финансах, производстве и здравоохранении уже двадцать лет: Walmart, Amazon и их сети поставщиков, банки с host-to-host каналом, автопром, дистрибьюторы — все требуют AS2.

В .NET до сих пор было два пути. Либо коммерческий AS2-шлюз — Cleo, Seeburger, BizTalk — отдельная коробка, отдельная лицензия, отдельная команда сопровождения. Либо Java-сервер с открытым кодом — OpenAS2, Mendelson Community — отдельный JVM-процесс рядом с вашим .NET-бэкендом, со своим inbox-каталогом, откуда документы надо ещё забирать джобой. В обоих случаях AS2 живёт сбоку от вашей интеграции, а не внутри неё.

redb.Route.AS2закрывает этот разрыв: AS2 становится обычным шагом маршрута в вашем .NET-процессе. Приняли конверт от партнёра, расшифровали, проверили подпись, отдали документ в pipeline — провалидировали, трансформировали, положили в Kafka или SQL — и вернули партнёру подписанную расписку. Один процесс, один деплой, одна панель наблюдаемости. Разберём, как это выглядит в коде, где применяется и почему нативный коннектор в ESB выигрывает у отдельного шлюза.

Что такое AS2 за одну минуту

AS2 (Applicability Statement 2, RFC 4130) — это протокол гарантированной доставки бизнес-документов между двумя сторонами через интернет. Технически это конверт:

  1. Полезная нагрузка (обычно EDI: X12 или EDIFACT, но может быть XML, JSON, что угодно) — сжимается (опционально), подписывается вашим приватным ключом и шифруется публичным сертификатом партнёра.

  2. Готовый S/MIME-конверт уходит партнёру обычным HTTP POST.

  3. Партнёр расшифровывает своим ключом, проверяет вашу подпись вашим сертификатом и отвечает MDN (Message Disposition Notification) — распиской. Подписанный MDN несёт Received-Content-MIC: криптографический хеш того, что партнёр реально получил.

Смысл MDN — юридически значимая неотрекаемость. Вы отправили заказ, партнёр прислал подписанную расписку с MIC, который совпадает с тем, что вы отправили: у вас есть доказательство доставки ровно того документа, а не другого. Именно поэтому AS2 стал стандартом де-факто там, где за документом стоят деньги и обязательства.

MDN бывает синхронный (расписка приходит в том же HTTP-ответе) и асинхронный (партнёр подтверждает приём и присылает MDN отдельным POST-ом позже, на ваш адрес). Оба режима — часть протокола, и оба нужны в жизни: крупные партнёры часто требуют именно асинхронный.

«А почему не просто HTTPS»

Резонный вопрос: если канал и так защищён TLS, зачем ещё подписывать и шифровать документ? Потому что TLS защищает канал, а AS2 — документ. TLS живёт ровно от вашего сокета до сокета партнёра и исчезает, как только байты легли на диск: в логе прокси, в inbox-каталоге, на балансировщике документ уже открыт. S/MIME-конверт AS2 остаётся подписанным и зашифрованным на всём пути и в покое — расшифровать его может только владелец приватного ключа, а подпись доказывает автора. И главное — TLS не даёт расписки: у HTTPS нет MDN с MIC, а именно он делает доставку неотрекаемой. AS2 обычно и ходит поверх HTTPS (as2s) — одно другому не мешает: канал шифрует TLS, документ и неотрекаемость обеспечивает S/MIME.

Читаемый маршрут: endpoint как строка

redb.Route — это Apache Camel под .NET: маршрут описывается как From → … → To, а endpoint — это URI-строка. Коннектор AS2 добавляет две схемы, as2 (HTTP) и as2s (HTTPS), и они читаются как предложение:

as2s://partner.example.com/as2?connectionFactory=walmart          # кому отправляем
as2:/inbound/orders?host=0.0.0.0&port=4080&connectionFactory=walmart   # где принимаем
as2:/as2/mdn?host=0.0.0.0&port=4081&mode=mdn&connectionFactory=walmart # куда партнёр шлёт асинхронный MDN

По URI сразу видно намерение: куда идём, на каком порту слушаем, какой партнёр. Сертификаты и алгоритмы в строке не живут — им там не место. Для этого есть отдельная сущность.

Партнёр — один объект, а не россыпь параметров

Обмен по AS2 — это всегда договорённость двух сторон: чьи сертификаты, какие AS2-идентификаторы, что подписываем и чем шифруем, какой режим MDN. Всё это — As2ConnectionFactory, зарегистрированный в реестре один раз по имени:

context.AddToRegistry("walmart", new As2ConnectionFactory
{
    OurCertificate     = ourPfx,     // наш сертификат с ПРИВАТНЫМ ключом — подписывает исходящее, расшифровывает входящее
    PartnerCertificate = theirCer,   // публичный сертификат партнёра — шифрует исходящее, проверяет их подпись
    As2From = "OUR-AS2-ID",
    As2To   = "WALMART-AS2-ID",
    PartnerUrl = "https://partner.example.com/as2",

    // Профиль обмена — то, о чём договорились
    Sign = true, Encrypt = true, Compress = false,
    SignAlg = "sha-256", EncryptAlg = "aes-128-cbc",
    SignedMdn = true, MdnMode = As2MdnMode.Sync,
});

Маршруты ссылаются на партнёра по имени — ?connectionFactory=walmart в URI или .ConnectionFactory("walmart") в fluent-DSL. Добавили второго партнёра — зарегистрировали ещё один объект, URI маршрута не меняется. Сертификаты версионируются вместе с вашим приложением, а не лежат в keystore отдельного шлюза, о существовании которого знает один человек в команде.

Один маршрут — три окружения

URI endpoint'а — строка, а в строке работают плейсхолдеры {{ключ}}, которые redb.Route подставляет из IConfiguration на этапе сборки маршрута. Партнёрский адрес в dev, staging и prod разный — но маршрут один:

From("direct://outbound")
    .To(As2.Send("{{walmart.as2.url}}").ConnectionFactory("walmart"));
# appsettings.Development.json → "walmart.as2.url": "https://sandbox.partner/as2"
# appsettings.Production.json  → "walmart.as2.url": "https://edi.partner.com/as2"

Хост, порт приёмника, имя партнёра выносятся в конфигурацию, а не хардкодятся в маршрут. Секреты — пароль от PFX — приходят из того же слоя (переменные окружения, user-secrets) и не оседают в исходниках. Один и тот же собранный образ едет из dev в prod, меняется только конфиг.

Отправка: подписали, зашифровали, получили расписку

From("direct://outbound")
    .To(As2.Send("https://partner.example.com/as2").ConnectionFactory("walmart"));

За этой одной строкой — сжатие (если включено), подпись вашим ключом, шифрование сертификатом партнёра, POST и разбор ответного MDN. Результат обмена коннектор кладёт на exchange.Out, чтобы маршрут мог принять решение:

Заголовок на exchange.Out

Что значит

redbAs2.mdnDisposition

вердикт партнёра, напр. automatic-action/MDN-sent-automatically; processed

redbAs2.signatureValid

bool — подпись MDN проверена

redbAs2.mdnMicMatch

bool — партнёр получил ровно то, что мы отправили, без искажений

И тут же, в том же маршруте, разветвляемся по результату:

From("direct://outbound")
    .To(As2.Send("https://partner/as2").ConnectionFactory("walmart"))
    .Choice()
        .When(e => e.Out!.GetHeader<bool>(As2Headers.MdnMicMatch))
            .Log("доставлено и подтверждено")
        .Otherwise()
            .To("direct://delivery-alert");   // MIC не сошёлся или отрицательный MDN — эскалация

Коннектор не бросает исключение на отрицательный MDN или расхождение MIC — он выкладывает факт и оставляет решение маршруту. Для одного партнёра расхождение MIC — повод немедленно поднять тревогу, для другого — записать в журнал и продолжить. Это политика вашего процесса, а не жёстко зашитое поведение шлюза.

Приём: AS2-сервер как источник маршрута

From(As2.Receive("/inbound/orders").Host("0.0.0.0").Port(4080).ConnectionFactory("walmart"))
    .Unmarshal(...)                 // разбор EDI — ваш формат
    .To("direct://process-order");

From(As2.Receive(...)) поднимает AS2-сервер. Пришедший конверт коннектор расшифровывает вашим приватным ключом, проверяет подпись сертификатом партнёра, распаковывает — и в маршрут попадает чистый бизнес-документ. Его реальный тип лежит на Message.ContentType (например, application/edi-x12), а метаданные обмена — под redbAs2.*:

Заголовок

Что значит

redbAs2.mic / redbAs2.micalg

вычисленный Message Integrity Check и его алгоритм

redbAs2.signatureValid

входящая подпись проверена сертификатом партнёра

redbAs2.remoteAddress

IP отправителя

redbAs2.partner

имя сопоставленного connectionFactory

Синхронный MDN коннектор формирует и возвращает сам. AS2-заголовки самого протокола (AS2-FromAS2-ToMessage-IDSubject) кладутся на сообщение как есть. Обёрточный Content-Type S/MIME-конверта в заголовки не просачивается — маршрут видит тип реального документа, а не транспортную обёртку.

Асинхронный MDN

Крупные партнёры часто требуют асинхронную расписку: приняли — ответили 200, а подписанный MDN прислали отдельным POST-ом позже. Отправитель регистрирует исходящий Message-ID, а входящий MDN коррелируется по Original-Message-ID:

// В профиле партнёра
MdnMode = As2MdnMode.Async,
AsyncMdnUrl = "https://our-host:4081/as2/mdn",

// Маршруты
From(As2.Receive("/inbound").Host("0.0.0.0").Port(4080).ConnectionFactory("walmart"))
    .To("direct://process");

From(As2.ReceiveMdn("/as2/mdn").Host("0.0.0.0").Port(4081).ConnectionFactory("walmart"))
    .Process(e =>
    {
        var original = e.In.GetHeader<string>(As2Headers.MessageId);   // какой наш документ подтверждён
        var ok = e.In.GetHeader<bool>(As2Headers.MdnMicMatch);         // и подтверждён ли целым
    });

Отдельный endpoint под входящие MDN — это тоже просто From в маршруте, и приходящая расписка так же становится сообщением, с которым можно работать: обновить статус заказа, снять с ретрая, закрыть сагу.

Матрица алгоритмов

Партнёры договариваются о конкретных алгоритмах, и коннектор поддерживает стандартный набор:

Параметр

Значения

SignAlg

sha-1sha-256 (по умолчанию), sha-384sha-512

EncryptAlg

aes-128-cbc (по умолчанию), aes-192-cbcaes-256-cbc3des

Compress

true / false (сжатие по RFC 3274)

Неподдерживаемый алгоритм отваливается на этапе сборки маршрута, а не в рантайме на первом же сообщении партнёру. Ошибка конфигурации видна при старте, а не в три часа ночи по логам отбойника.

Криптография под капотом — MimeKit (на Bouncy Castle). Это тот же криптографический фундамент, на котором работает весь мир AS2: и Apache camel-as2, и OpenAS2, и Mendelson стоят на Bouncy Castle. Мы садимся на общую основу, на которой стороны и интеропятся, а не изобретаем свой S/MIME.

Чем это отличается от шлюза

AS2 в .NET-проекте закрывали тремя способами. Разница не в том, «умеет или нет» — умеют все, протокол один. Разница в том, где живёт AS2 относительно вашей логики.

Коммерческий шлюз

Java-сервер (OpenAS2/Mendelson)

redb.Route.As2

Процесс

отдельная коробка

отдельный JVM рядом

ваш .NET-процесс

Принятый документ

файл в inbox-каталоге

файл в inbox-каталоге

сообщение в маршруте

Как забрать

джоба-подборщик

джоба-подборщик

сразу в pipeline

Деплой

своя инсталляция

своя инсталляция

вместе с приложением

Наблюдаемость

своя панель

логи JVM

общие трейсы и статистика

Дальше по обработке

вне шлюза, руками

вне сервера, руками

те же EIP в том же маршруте

Лицензия

платная

открытая

открытая, без ключа

Отдельный шлюз оправдан, когда AS2-контур сознательно вынесен в отдельную зону — например, в DMZ под управлением другой команды. Когда же документ всё равно уходит в ваш .NET-бэкенд на обработку, промежуточная коробка — это лишний прыжок, лишний каталог и лишний компонент в схеме аудита.

Где это применяется и почему именно нативный коннектор

AS2 нужен там, где документ несёт обязательство, а партнёр диктует канал. Несколько типичных ситуаций.

Поставщик в крупную розницу. Чтобы отгружать в Walmart, Target, Amazon Vendor, поставщик обязан принимать заказы (850) и слать счета (810) и ASN (856) по AS2 с подписанным MDN. Раньше под это ставили отдельный шлюз; теперь From(As2.Receive(...)) принимает заказ прямо в тот маршрут, который его валидирует и кладёт в вашу ERP.

3PL и логистика. Склад и перевозчик обмениваются статусами отгрузки, подтверждениями приёмки, инвентаризацией. Объём — потоковый, и держать под него отдельную Java-коробку с inbox-каталогом, из которого документы вычёрпывает cron-джоба, — лишнее звено. Документ должен течь, а не лежать в папке.

Здравоохранение. X12-транзакции HIPAA (claims, remittance) между плательщиками и провайдерами идут по AS2 с жёсткими требованиями к подписи и шифрованию. Тот же профиль — подпись, шифрование, подписанный MDN.

Финансы и платежи. Платёжные и расчётные документы EDI — платёжное поручение и авизо о переводе (X12 820/824) — ходят между корпоративными клиентами и их банками по AS2 там, где банк предлагает host-to-host канал. AS2 здесь не заменяет SWIFT или EBICS на межбанке, а закрывает EDI-слой: документооборот корпорации с банком и платёжные извещения в цепочке поставок, где оплата — такой же EDI-документ, как заказ и счёт. Для финтеха важна ровно неотрекаемость AS2: подписанный MDN с MIC — это доказательство, что банк получил именно это платёжное поручение, а не другое.

Производство и снабжение. Автопром и промышленные supply-chain сети гоняют по AS2 заказы, план-графики поставок, отгрузочные уведомления (EDIFACT/X12) между OEM и поставщиками разных ярусов. Обмен обязательный: без электронного документооборота поставщика к сети просто не подключат.

Страхование. Enrollment, claims, remittance (X12) между плательщиками, брокерами и провайдерами — тот же профиль подписи и шифрования, те же требования к расписке.

Консолидация шлюза. У вас уже есть коммерческий AS2-шлюз, но это отдельная коробка со своей лицензией, своим циклом патчей и своим мониторингом, живущая параллельно .NET-бэкенду, который и делает всю бизнес-логику. AS2-коннектор сворачивает этот контур внутрь приложения.

Почему нативный коннектор в ESB, а не отдельный шлюз рядом:

  • Документ не лежит, а течёт. У отдельного шлюза принятый файл попадает в inbox-каталог, откуда его надо забрать. У коннектора принятый документ — это сообщение в маршруте: сразу валидация, трансформация, маршрутизация. Нет промежуточной папки и джобы-подборщика.

  • Один процесс, один деплой. Нет второй коробки, которую надо ставить, патчить, мониторить и объяснять аудиту. AS2-эндпоинт поднимается на общем Kestrel-хосте вместе с остальными HTTP-маршрутами процесса.

  • Одна плоскость наблюдаемости. AS2-эндпоинты дают статистику и health (видны в дашборде redb.Tsak) и распределённые трейсы наравне с остальными коннекторами: producer открывает Client-спан, consumer — Consumer-спан, связанный по W3C traceparent. Обмен с партнёром виден в той же Jaeger-трассе, что и путь документа дальше по маршруту.

  • Композиция с EIP. Это и есть главное. AS2 — не остров, а шаг. Принятый документ можно провести через весь каталог паттернов redb.Route: провалидировать по схеме, трансформировать XSLT, разложить Splitter'ом, обогатить, продублировать WireTap'ом в архив, положить в Kafka и в SQL одновременно.

AS2 как один шаг сквозного маршрута

Ценность коннектора видна, когда AS2 стоит не сам по себе, а в цепочке. Приняли заказ от партнёра, трансформировали, разложили и разослали дальше — одним маршрутом:

From(As2.Receive("/inbound/orders").Host("0.0.0.0").Port(4080).ConnectionFactory("walmart"))
    .Validate(...)                               // документ соответствует схеме
    .Xslt("styles/x12-to-canonical.xsl")         // X12 → ваша каноническая модель
    .WireTap("sftp://archive/edi?...")           // копия в архив с retention
    .To("kafka://orders?brokers=...")            // в шину для обработки
    .To("sql:INSERT INTO inbound_orders ...");   // и в БД аудита

Тот же документ, что пришёл в зашифрованном AS2-конверте, за один проход оказывается провалидированным, трансформированным, заархивированным, в Kafka и в SQL — и всё это видно в одной трассе. Отдельный шлюз дал бы вам файл в папке; здесь вы получаете полноценный конвейер, у которого AS2 — просто вход.

Исходящее направление симметрично: собрали документ из вашей системы, отправили партнёру, разобрали MDN, обновили статус — тоже один маршрут.

Полный контур: приняли заказ — отправили счёт

Онбординг поставщика к рознице — это два направления AS2, которые в redb.Route живут двумя маршрутами в одном процессе.

Входящее — заказ (EDI 850) от ритейлера:

From(As2.Receive("/inbound").Host("0.0.0.0").Port(4080).ConnectionFactory("walmart"))
    .Validate(typeof(X12OrderValidator))          // структура 850 корректна
    .Unmarshal(typeof(X12Format), typeof(Order))  // X12 → доменная модель
    .Process(async (e, ct) => await _orders.Accept((Order)e.In.Body!, ct))
    .To("kafka://orders-inbound?brokers={{kafka.brokers}}");

Заказ принят, провалидирован, разобран, сохранён и опубликован в шину — за один проход, и синхронный MDN ушёл ритейлеру автоматически. Дальше ваша система обрабатывает заказ своим темпом.

Исходящее — счёт (EDI 810) обратно партнёру, когда отгрузка готова:

From("kafka://invoices-ready?brokers={{kafka.brokers}}")
    .Marshal(typeof(X12Format))                   // доменная модель → X12 810
    .To(As2.Send("{{walmart.as2.url}}").ConnectionFactory("walmart"))
    .Choice()
        .When(e => e.Out!.GetHeader<bool>(As2Headers.MdnMicMatch))
            .Process(e => _invoices.MarkDelivered(e))
        .Otherwise()
            .To("direct://edi-ops-alert");

Оба направления используют один connectionFactory("walmart") — одни сертификаты, один профиль. Весь EDI-контур с партнёром — два маршрута и один объект партнёра, внутри вашего приложения, под вашей наблюдаемостью.

Обмен виден в общей трассе

AS2 у отдельного шлюза — чёрный ящик: партнёр говорит «не пришло», и вы идёте в чужие логи на чужой коробке. Здесь AS2-эндпоинты — часть общей наблюдаемости redb.Route. Producer открывает Client-спан, consumer — Consumer-спан, связанный с входящим документом по W3C traceparent. В одной Jaeger-трассе видно всё: пришёл конверт от партнёра → расшифрован и проверена подпись → провалидирован → трансформирован → ушёл в Kafka. Статистика эндпоинта — сколько принято, сколько отправлено, ошибки — в дашборде redb.Tsak, рядом с остальными коннекторами. Когда партнёр спорит о доставке, у вас на руках трасса конкретного Message-ID, а не «где-то в логах шлюза».

Что важно знать про AS2 на практике

Несколько вещей, на которые наступаешь ровно один раз.

MIC считается по каноническому виду, байт в байт. Message Integrity Check — это хеш подписанной части в CRLF-канонической форме, вычисленный ровно так, как его считала отправляющая сторона. Отсюда весь интероп-боль AS2: если каноникализация или алгоритм разошлись хоть на байт, MIC в MDN не совпадёт, и партнёр решит, что получил не то. Коннектор считает MIC по RFC 4130 и сверяет его на приёме и в разборе MDN — и именно это проверено против чужого сервера, а не на самом себе.

Sync или async — решает партнёр, не вы. Синхронный MDN проще: расписка в том же ответе, маршрут сразу знает исход. Но при большом объёме синхронный MDN держит HTTP-соединение до конца обработки, и крупные партнёры требуют асинхронный: приняли — ответили 200, MDN прислали отдельным POST-ом. Коннектор поддерживает оба; выбор — строка в профиле партнёра, а не переписывание маршрута.

Подписанный MDN — это про неотрекаемость, а не про галочку. SignedMdn = true значит, что расписка подписана ключом принимающей стороны и несёт MIC. Для документа, за которым стоит обязательство, это и есть доказательство: у вас на руках подписанное подтверждение, что партнёр получил именно этот документ целым. Неподписанный MDN — просто «дошло», чему в споре грош цена.

Content-Transfer-Encoding: binary. EDI-документы бинарны по духу, и партнёры обычно обмениваются в binary, а не base64 — меньше накладных расходов на большом объёме. Деталь профиля, но именно на ней часто спотыкаются самодельные реализации.

Сертификаты и секреты

AS2 стоит на паре ключей: ваш приватный (подпись исходящего, расшифровка входящего) и публичный сертификат партнёра (шифрование исходящего, проверка их подписи). В коннекторе это X509Certificate2 в As2ConnectionFactory — грузите из PKCS#12/PFX как удобно: из файла, из хранилища сертификатов Windows, из секрет-менеджера.

Сертификаты — часть конфигурации партнёра в реестре, а не строки в URI. Пароль от PFX, если он приходит через параметр endpoint'а, помечен [Sensitive] и вырезается из логов и из дашборда — секрет не утекает в трассу. Ротация партнёрского сертификата — это замена объекта в реестре; маршрут не трогается.

Интероп доказан, а не заявлен

Про AS2 легко написать «поддерживаем» и приложить юнит-тесты, которые гоняют коннектор против самого себя. Это доказывает внутреннюю непротиворечивость, но не то, что ваш конверт примет чужой софт. Настоящая проверка — обмен с независимой реализацией.

Коннектор проверен против живого OpenAS2 v4.9.0 (зрелый сервер на Bouncy Castle) в Docker, в обоих направлениях, профилем «подпись SHA-256 + шифрование AES-128-CBC + подписанный MDN»:

  • redb → OpenAS2. Наш producer отправил документ, OpenAS2 своими логами подтвердил: расшифровал нашим конвертом, проверил нашу подпись, сохранил документ, вернул положительный подписанный MDN. Наш разбор MDN проверил его подпись и подтвердил совпадение Received-Content-MIC.

  • OpenAS2 → redb. OpenAS2 собрал подписанный и зашифрованный документ и отправил нашему consumer'у. Мы расшифровали приватным ключом, проверили подпись OpenAS2, отдали EDI в маршрут и вернули подписанный MDN, который OpenAS2 принял и скоррелировал со своим ожиданием.

Это закрывает «сложную часть» AS2 в обе стороны: вычисление MIC по RFC 4130, структуру S/MIME и обработку MDN — на приём и на отправку — принимает настоящий, чужой партнёр.

Тесты открыты. Как именно проверяли — три слоя (крипто-раунд-трипы и MIC, e2e-петля продюсер→консьюмер через живой Kestrel, интероп против OpenAS2) — расписано в файле TESTING.md коннектора на GitHub. Стенд интеропа (docker-compose с OpenAS2, конфиг, сертификаты) воспроизводится одной командой; интероп-тест gated — обычный прогон зелёный и без контейнера.

Частые вопросы

Несколько партнёров на одном порту? Да. Приёмные endpoint'ы разных партнёров живут на общем Kestrel-хосте, разведённые по пути (/inbound/walmart/inbound/target), каждый со своим connectionFactory. Один сервер, несколько партнёрств.

Крупные файлы? Тело сообщения — байты, и поток идёт через обычный HTTP-конвейер, а не собирается в строку. Для действительно тяжёлых выгрузок партнёры обычно переходят на SFTP — который в redb.Route тоже коннектор и стоит в том же маршруте.

mTLS / клиентские сертификаты на транспорте? AS2 сам аутентифицирует документ подписью, но некоторые партнёры дополнительно требуют TLS client-auth. Схема as2s идёт поверх того же Kestrel-хоста, что и HTTP-коннектор, с его настройками TLS.

Что при рестарте? Асинхронный MDN коррелируется по Original-Message-ID; исходящие, ждущие расписку, — это состояние вашего маршрута, и хранится оно там, где вы держите состояние саги или идемпотентности, а не в памяти коннектора.

Установка

services.AddRedbRoute(route =>
{
    route.Services.AddRedbRouteAs2();
    route.AddRouteBuilder<MyRoutes>();
});

AddRedbRouteAs2() регистрирует схемы as2 / as2s и переиспользует общий Kestrel-хост процесса — AS2-маршрут и обычный HTTP-маршрут в одном воркере делят один сервер и не спорят за порт.

Пакет — redb.Route.As2 на NuGet, исходники и полное описание DSL — в README коннектора. AS2 — очередной транспорт в семействе redb.Route, наравне с Kafka, RabbitMQ, IBM MQ, SFTP и остальными: тот же From → … → To, те же EIP, та же наблюдаемость. Разница только в том, что на входе и выходе — подписанный и зашифрованный конверт, которого ждёт ваш торговый партнёр.


Другие мои статьи — redb.ru/articles, ещё — на Хабре.

If this was useful — a ⭐ on GitHub helps others find it.