SOAP, WSDL и WS-*¶
Стек классической SOA. SOAP задаёт формат сообщений, WSDL описывает контракт сервиса, WS-* добавляет безопасность, гарантии доставки и транзакции.
Аналогия: WSDL - меню ресторана (какие блюда есть и куда отдавать заказ), SOAP - бланк заказа строгой формы.
Зачем SOAP¶
- Строгий контракт. Описание сервиса машиночитаемое, по нему генерируется клиент на любом языке. Сообщение неправильного формата отсекается проверкой схемы ещё до бизнес-логики.
- Защита самого сообщения. Подпись и шифрование лежат внутри сообщения, а не только в канале. Подписанное сообщение можно хранить как юридически значимый документ.
- Не зависит от транспорта. Одно и то же сообщение может идти по HTTP, через очередь JMS или через шину.
- Готовые стандарты для гарантий. Гарантированная доставка и транзакции описаны стандартами, их не придумывают в каждом проекте заново.
Когда SOAP не нужен¶
- новый публичный API, особенно для браузера и мобильных клиентов -- REST или gRPC проще и легче
- внутренние сервисы одной команды: строгий контракт замедляет изменения, каждая правка ломает сгенерированных клиентов
- большие объёмы и низкие задержки: XML многословный и медленно парсится
SOAP: формат сообщения¶
SOAP - протокол обмена сообщениями в формате XML. Стандартизирован W3C: SOAP 1.1 (2000), SOAP 1.2 (2003).
Из чего состоит сообщение¶
Envelope - конверт, корневой элемент
├── Header - необязательно: служебное (авторизация, подпись, ID транзакции)
└── Body - сам вызов, ответ или ошибка (Fault)
Пример: запрос и ответ¶
Сервис банка «дай курс валюты». Запрос (SOAP 1.1 поверх HTTP):
POST /CurrencyService HTTP/1.1
Host: api.bank.example
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://bank.example/GetRate"
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cur="http://bank.example/currency">
<soap:Header>
<cur:Auth>
<cur:Token>abc123</cur:Token>
</cur:Auth>
</soap:Header>
<soap:Body>
<cur:GetRate>
<cur:From>USD</cur:From>
<cur:To>RUB</cur:To>
</cur:GetRate>
</soap:Body>
</soap:Envelope>
Ответ:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cur="http://bank.example/currency">
<soap:Body>
<cur:GetRateResponse>
<cur:Rate>92.15</cur:Rate>
<cur:Date>2026-10-10</cur:Date>
</cur:GetRateResponse>
</soap:Body>
</soap:Envelope>
Ошибка - стандартный элемент Fault, обычно с HTTP 500:
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode> <!-- виноват клиент (Client) или сервер (Server) -->
<faultstring>Unknown currency: XYZ</faultstring>
<detail>...подробности в формате из WSDL...</detail>
</soap:Fault>
</soap:Body>
Чем отличается от REST-запроса¶
- Один URL на весь сервис, всегда POST. В REST действие задаётся URL и методом (
GET /rates/USD). В SOAP сервер узнаёт, что делать, изBody(GetRate) и заголовкаSOAPAction. - HTTP - только транспорт. SOAP не использует коды ответа, кеширование и методы HTTP. Он может идти и через JMS, и через SMTP.
- Namespace'ы (
xmlns:...) нужны, чтобыGetRateодного банка не путался сGetRateдругого. Отсюда многословность SOAP. - Header - место для служебного: подпись, шифрование, ID транзакции. Тело запроса при этом не меняется.
WSDL: контракт сервиса¶
WSDL - XML-файл с описанием сервиса: какие у него операции, какие данные они принимают и возвращают, по какому адресу и как их вызывать.
| Раздел WSDL | Что описывает | Аналогия из кода |
|---|---|---|
types |
Структуры данных (на XML Schema, XSD) | классы DTO |
message |
Вход и выход каждой операции | параметры и возвращаемый тип |
portType |
Список операций | интерфейс |
binding |
Как передавать: версия SOAP, транспорт, стиль кодирования | протокол |
service / port |
Адрес сервиса | URL |
Первые три раздела описывают, что умеет сервис, последние два - как и где его вызвать.
Пример для того же сервиса курсов:
<definitions name="CurrencyService"
targetNamespace="http://bank.example/currency"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://bank.example/currency"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- 1. Типы данных -->
<types>
<xsd:schema targetNamespace="http://bank.example/currency" elementFormDefault="qualified">
<xsd:element name="GetRate">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="From" type="xsd:string"/>
<xsd:element name="To" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="GetRateResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Rate" type="xsd:decimal"/>
<xsd:element name="Date" type="xsd:date"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>
<!-- 2. Сообщения: вход и выход -->
<message name="GetRateRequest">
<part name="parameters" element="tns:GetRate"/>
</message>
<message name="GetRateResponse">
<part name="parameters" element="tns:GetRateResponse"/>
</message>
<!-- 3. Интерфейс: список операций -->
<portType name="CurrencyPort">
<operation name="GetRate">
<input message="tns:GetRateRequest"/>
<output message="tns:GetRateResponse"/>
</operation>
</portType>
<!-- 4. Как передавать: SOAP поверх HTTP, document/literal -->
<binding name="CurrencyBinding" type="tns:CurrencyPort">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="GetRate">
<soap:operation soapAction="http://bank.example/GetRate"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<!-- 5. Где: адрес сервиса -->
<service name="CurrencyService">
<port name="CurrencyPort" binding="tns:CurrencyBinding">
<soap:address location="https://api.bank.example/CurrencyService"/>
</port>
</service>
</definitions>
Руками WSDL почти не пишут и не читают целиком - его генерируют инструменты.
Стили binding¶
- rpc/encoded - старый стиль, типы зашиты прямо в сообщение. Плохо совместим между платформами, устарел.
- document/literal - в
Bodyлежит XML-документ, который проверяется по XSD из WSDL. Стандарт де-факто, его требует профиль совместимости WS-I Basic Profile. - document/literal wrapped - то же, но корневой элемент
Bodyназывается как операция (<GetRate>). Так делают почти все, включая пример выше.
Как этим пользуются¶
Код генерируется по контракту. Есть два подхода:
- Code-first - пишешь класс, WSDL генерируется сам и доступен по адресу
https://.../CurrencyService?wsdl. - Contract-first - сначала согласуют WSDL (например, банк и партнёр), потом обе стороны генерируют по нему код. В крупных интеграциях правильнее этот вариант: контракт - юридически значимая договорённость, и он не должен меняться от рефакторинга чьего-то класса.
Code-first на Java (JAX-WS):
@WebService
public class CurrencyService {
@WebMethod
public BigDecimal getRate(String from, String to) { ... }
}
Клиент, сгенерированный из WSDL, вызывает удалённый сервис как обычный метод:
CurrencyPort port = new CurrencyService().getCurrencyPort();
BigDecimal rate = port.getRate("USD", "RUB"); // внутри: XML -> HTTP POST -> XML -> объект
Инструменты:
- Java - Apache CXF, Jakarta XML Web Services (Metro), Spring Web Services. Встроенный JAX-WS убрали из JDK в Java 11, теперь это отдельные зависимости.
- .NET -
dotnet-svcutil, «Add Service Reference». - Python -
zeep. - Отладка - SoapUI: загружаешь WSDL, и он строит формы запросов для каждой операции.
WS-*: расширения поверх SOAP¶
WS-* - несколько десятков стандартов W3C и OASIS поверх SOAP. Каждый добавляет свой блок в Header, тело
сообщения не меняется:
Envelope
├── Header
│ ├── wsa:* - WS-Addressing: кому, от кого, куда отвечать
│ ├── wsrm:* - WS-ReliableMessaging: номер сообщения в последовательности
│ └── wsse:* - WS-Security: подпись, сертификат, токен
└── Body - бизнес-запрос как есть
Поэтому стандарты можно включать по одному или вместе, а транспорт им не важен. Подпись остаётся проверяемой, даже если сообщение прошло через шину и очередь.
Атрибут mustUnderstand="true" на заголовке значит: не умеешь обрабатывать этот заголовок - не обрабатывай сообщение,
верни ошибку. Так сервер не может молча пропустить проверку подписи.
Основные стандарты:
| Стандарт | Что даёт |
|---|---|
| WS-Security | Подпись и шифрование самого сообщения, токены (логин, сертификат X.509, SAML), защита от повтора по времени |
| WS-Addressing | ID сообщения, адрес для ответа и ошибки - асинхронный обмен |
| WS-ReliableMessaging | Гарантированная доставка: без потерь, без дублей, по порядку |
| WS-AtomicTransaction | Распределённые транзакции по 2PC |
| WS-BusinessActivity | Долгие процессы с компенсациями - по сути стандартизованная saga |
| WS-Policy | Требования сервиса прямо в WSDL: «подписывать сообщения», «шифровать Body» |
| WS-Trust, WS-Federation | Выдача токенов и единый вход между организациями |
| MTOM | Передача файлов отдельной частью сообщения, без base64 внутри XML |
WS-Security и HTTPS¶
| HTTPS (TLS) | WS-Security | |
|---|---|---|
| Что защищает | Канал между двумя точками | Само сообщение |
| Через посредников | На каждом узле расшифровывается заново | Подпись проверяется от отправителя до получателя |
| После доставки | Защиты больше нет | Подписанное сообщение можно хранить как доказательство |
| Юридическая значимость | Нет | Да, если подпись квалифицированная |
Поэтому банки и госсистемы держатся за WS-Security: подписанное сообщение - это электронный документ.
Чем WS-* заменили сейчас¶
| WS-* | Современный аналог |
|---|---|
| WS-Security | TLS / mTLS. Для защиты самого сообщения - JWS и JWE |
| WS-Trust, WS-Federation | OAuth 2.0, OpenID Connect, SAML |
| WS-Addressing | HTTP-заголовки, correlation ID, заголовки сообщений брокера |
| WS-ReliableMessaging | Kafka, RabbitMQ с подтверждениями, идемпотентность, outbox |
| WS-AtomicTransaction | Saga и outbox вместо распределённых транзакций |
| WS-Policy | securitySchemes в OpenAPI |
| MTOM | multipart/form-data, presigned URL на объектное хранилище |
Семейство критикуют за размер и сложность: десятки пересекающихся спецификаций, подпись XML ломается от лишнего пробела, реализации разных вендоров плохо совместимы между собой. Из всего набора на практике чаще всего встречаются WS-Security, WS-Addressing и MTOM.
Где SOAP встречается сейчас¶
Новые публичные API на SOAP почти не делают, но в интеграциях он встречается постоянно:
- госсистемы РФ - СМЭВ, ФНС, Росреестр: сообщения подписываются XML-подписью по ГОСТ
- банки и платёжные системы - межбанковские интеграции, процессинг
- корпоративный софт - SAP, 1С, Oracle, Microsoft Dynamics, WCF в .NET
- Windows-инфраструктура - WinRM (удалённый PowerShell) работает на WS-Management
- IP-камеры ONVIF - SOAP, WS-Discovery и WS-Security UsernameToken
SOAP и REST¶
| SOAP + WSDL | REST + JSON | |
|---|---|---|
| Что это | Протокол со строгими правилами | Архитектурный стиль |
| Формат | Только XML | Обычно JSON |
| Контракт | WSDL, обязателен | OpenAPI, по желанию |
| Адресация | Один URL, всегда POST | Ресурсы в URL и HTTP-методы |
| Ошибки | soap:Fault |
HTTP-коды |
| Кеширование HTTP | Не используется | Работает для GET |
| Безопасность | WS-Security на уровне сообщения | TLS и OAuth/JWT на уровне канала |
| Гарантии доставки, транзакции | Стандарты WS-* | Решаются отдельно: идемпотентность, outbox, saga |
| Генерация клиента | Из коробки | Через генераторы по OpenAPI |