Статьи

Службы маршрутизации gSoap и WCF не являются друзьями

Одним из распространенных шаблонов с веб-сервисами является сервис маршрутизатора. Это может скрыть логику маршрутизации от клиента или помочь с балансировкой нагрузки. Wcf 4 поставляется с библиотеками и примерами, которые помогут очень быстро создать такой маршрутизатор.

Стивен Лидиг недавно уведомил меня о проблеме, которая возникает, когда клиент gSoap вызывает Wcf-маршрутизатор. gSoap — очень популярный стек веб-сервисов CPP.

Это было развертывание:

Клиент gSoap отправлял это сообщение маршрутизатору:

<soap:Envelope xmlns="http://tempuri.org/" xmlns:p="http://myNs/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Body>
    <AddUser>
      <user xsi:type="p:User">
        <name>yaron</name>
      </user>
    </AddUser>
  </soap:Body>
</soap:Envelope>

Если бы маршрутизатор вызывал службу, используя конечную точку http, все работало бы. Если бы он использовал конечную точку tcp, он мог бы отправить сообщение, но реализация службы никогда не вызывалась бы, и служба сообщала бы об этом исключении:

Средство форматирования выдало исключение при попытке десериализации сообщения: при попытке десериализации параметра http://tempuri.org/:user произошла ошибка . Сообщение InnerException «Элемент» http://tempuri.org/:user содержит данные контракта о данных «Пользователь». Десериализатор не знает ни одного типа, который соответствует этому контракту. Добавьте тип, соответствующий «Пользователю», в список известных типов — например, с помощью атрибута KnownTypeAttribute или добавив его в список известных типов, переданных в DataContractSerializer. ‘. Пожалуйста, смотрите InnerException для более подробной информации. Анализ

Нам нужно задать два вопроса: почему происходит эта ошибка? Почему не с Http?

Сначала мы должны взглянуть на сообщение, которое маршрутизатор отправил службе. Вот как это выглядит в журнале Wcf:

<soap:Envelope xmlns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Body>
    <AddUser>
      <user xsi:type="p:User">
        <name>yaron</name>
      </user>
    </AddUser>
  </soap:Body>
</soap:Envelope>

Чего-то не хватает — объявление префикса «p» (xmlns: p = http: // myNs / ), которое присутствовало в сообщении от клиента к маршрутизатору, не появляется. Это делает это сообщение недействительным, так как оно ссылается на необъявленный префикс «p» в атрибуте производного типа «p: User». Но почему маршрутизатор отправил неверное сообщение, когда клиент отправил ему хорошее? А почему не с Http?

Давайте посмотрим, как Wcf маршрутизирует сообщения. Мы можем использовать отражатель для проверки System.ServiceModel.Routing.SoapProcessingBehavior + SoapProcessingInspector.MarshalMessage ():

internal Message MarshalMessage(Message source, Uri to, MessageVersion targetVersion)
{
    Message message;
    MessageVersion version = source.Version;
    if ((version == targetVersion) && !RoutingUtilities.IsMessageUsingWSSecurity(understoodHeaders))
    {
        ...
        return source;
    }
    ...
    else
    {
        XmlDictionaryReader readerAtBodyContents = source.GetReaderAtBodyContents();
        message = Message.CreateMessage(targetVersion, headers.Action, readerAtBodyContents);
    }

    this.CloneHeaders(message.Headers, headers, to, understoodHeadersSet);    
    return message;
} 

Wcf использует эту логику:

  • Если входное сообщение (от клиента) и выходное сообщение (на сервер) имеют одинаковую версию и безопасность не задействована, то примите ввод как есть и отправьте его. Это объясняет, почему дело Http работает.
  • В противном случае возьмите тело входного сообщения и скопируйте его в новое сообщение. Затем скопируйте пользовательские заголовки пользователя.

Поскольку префикс «p» определяется не под телом, а под корневым элементом «envelope», этот префикс не отправляется, что делает сообщение недействительным. Это объясняет ошибку netTcp.

Почему Wcf ведет себя так?

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

Какое должно быть правильное поведение?

Чтобы принять во внимание как функциональные требования, так и требования к производительности, я бы рекомендовал следующий подход:

  • Скопируйте все объявления пространства имен из корневых элементов Envelope и Body исходного сообщения в соответствующие элементы нового сообщения (включая пространство имен по умолчанию).
  • Не перегружайте предыдущие префиксы новыми определениями в обоих элементах.

Да, я могу вспомнить несколько крайних случаев, когда эта схема не работает. Но более комплексное решение будет стоить производительности.

Вывод

В настоящее время клиент gSoap по умолчанию не может вызвать маршрутизатор Wcf по умолчанию при использовании мостового протокола , что является реальной проблемой взаимодействия.

И просто чтобы уточнить, этот веб-сервис не определяет никаких производных (= унаследованных = известных) типов. По какой-то причине логика генерации сообщений по умолчанию в gSoap использует тип xsi: даже для базовых типов. Это не запрещено, поскольку используется правильное имя типа, хотя для этого нет реальной причины. В любом случае это делает этот случай довольно распространенным и не ограничивается услугами с производными типами.

И да, это одна из причин, почему не всем нравится Xml.