Vue d’ensemble de la couche de canal

La couche de canal fournit une abstraction du canal de transport, ainsi que des messages envoyés sur le canal. Il inclut également des fonctions pour la sérialisation des types de données C vers et depuis des structures SOAP. La couche de canal permet un contrôle total des communications au moyen de messages constitués de données envoyées ou reçues et contenant des corps et des en-têtes, ainsi que des canaux qui extraitnt les protocoles d’échange de messages et fournissent des propriétés pour la personnalisation des paramètres.

Message

Un message est un objet qui encapsule des données réseau, en particulier des données transmises ou reçues sur un réseau. La structure du message est définie par SOAP, avec un ensemble discret d’en-têtes et un corps de message. Les en-têtes sont placés dans une mémoire tampon et le corps du message est lu ou écrit à l’aide d’une API de flux.

Diagramme montrant l’en-tête et le corps d’un message.

Bien que le modèle de données d’un message soit toujours le modèle de données XML, le format de fil réel est flexible. Avant qu’un message ne soit transmis, il est encodé à l’aide d’un encodage particulier (tel que Text, Binary ou MTOM). Pour plus d’informations sur les encodages , consultez WS_ENCODING .

Diagramme montrant plusieurs formats d’encodage de message.

Canal

Un canal est un objet utilisé pour envoyer et recevoir des messages sur un réseau entre deux points de terminaison ou plus.

Les canaux ont des données associées qui décrivent comment traiter le message lors de son envoi. L’envoi d’un message sur un canal revient à le placer dans une goulotte : le canal inclut les informations où le message doit être envoyé et comment l’y parvenir.

Diagramme montrant les canaux pour les messages.

Les canaux sont classés en types de canaux. Un type de canal spécifie quelle direction les messages peuvent circuler. Le type de canal identifie également si le canal est de session ou sans session. Une session est définie comme un moyen abstrait de mettre en corrélation des messages entre deux ou plusieurs parties. Un canal de session est un exemple de canal TCP, qui utilise la connexion TCP comme implémentation de session concrète. UDP est un exemple de canal sans session, qui n’a pas de mécanisme de session sous-jacent. Bien que HTTP ait des connexions TCP sous-jacentes, ce fait n’est pas directement exposé via cette API et, par conséquent, HTTP est également considéré comme un canal sans session.

Diagramme montrant les types de canaux session et sans session.

Bien que les types de canaux décrivent la direction et les informations de session d’un canal, ils ne spécifient pas comment le canal est implémenté. Quel protocole le canal doit-il utiliser ? À quel point le canal doit-il essayer de remettre le message ? Quel type de sécurité est utilisé ? S’agit-il d’une monodiffusion ou d’une multidiffusion ? Ces paramètres sont appelés « liaison » du canal. La liaison se compose des éléments suivants :

Diagramme montrant une liste de propriétés de canal.

Écouteur

Pour commencer à communiquer, le client crée un objet Channel. Mais comment le service obtient-il son objet Channel ? Pour ce faire, il crée un écouteur. La création d’un écouteur nécessite les mêmes informations de liaison que celles nécessaires pour créer un canal. Une fois qu’un écouteur a été créé, l’application peut accepter les canaux de l’écouteur. Étant donné que l’application peut prendre du retard dans l’acceptation des canaux, les écouteurs conservent généralement une file d’attente de canaux prêts à accepter (jusqu’à un certain quota).

Diagramme montrant les canaux dans la file d’attente de l’écouteur.

Lancement de la communication (client)

Pour lancer la communication sur le client, utilisez la séquence suivante.

WsCreateChannel
for each address being sent to
{
    WsOpenChannel           // open channel to address
    // send and/or receive messages
    WsCloseChannel          // close channel
    WsResetChannel?         // reset if opening again
}
WsFreeChannel

Acceptation de la communication (serveur)

Pour accepter les communications entrantes sur le serveur, utilisez la séquence suivante.

WsCreateListener
WsOpenListener
for each channel being accepted (can be done in parallel)
{
    WsCreateChannelForListener
    for each accept
    {
        WsAcceptChannel     // accept the channel
        // send and/or receive messages
        WsCloseChannel      // close the channel
        WsResetChannel?     // reset if accepting again
    }
    WsFreeChannel
}
WsCloseListener
WsFreeListener

Envoi de messages (client ou serveur)

Pour envoyer des messages, utilisez la séquence suivante.

WsCreateMessageForChannel
for each message being sent
{
    WsSendMessage       // send message
    WsResetMessage?     // reset if sending another message
}
WsFreeMessage

La fonction WsSendMessage n’autorise pas la diffusion en continu et suppose que le corps ne contient qu’un seul élément. Pour éviter ces contraintes, utilisez la séquence suivante au lieu de WsSendMessage.

WsInitializeMessage     // initialize message to WS_BLANK_MESSAGE
WsSetHeader             // serialize action header into header buffer
WsAddressMessage?       // optionally address message
for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

La fonction WsWriteBody utilise la sérialisation pour écrire les éléments du corps. Pour écrire les données directement dans l’enregistreur XML, utilisez la séquence suivante au lieu de WsWriteBody.

WS_MESSAGE_PROPERTY_BODY_WRITER     // get the writer used to write the body
WsWriteStartElement
// use the writer functions to write the body
WsWriteEndElement
// optionally flush the body
WsFlushBody?        

La fonction WsAddCustomHeader utilise la sérialisation pour définir les en-têtes sur la mémoire tampon d’en-tête du message. Pour utiliser l’enregistreur XML afin d’écrire un en-tête, utilisez la séquence suivante au lieu de WsAddCustomHeader.

WS_MESSAGE_PROPERTY_HEADER_BUFFER   // get the header buffer 
WsCreateWriter                      // create an xml writer
WsSetOutputToBuffer                 // specify output of writer should go to buffer
WsMoveWriter*                       // move to inside envelope header element
WsWriteStartElement                 // write application header start element
// use the writer functions to write the header 
WsWriteEndElement                   // write appilcation header end element

Réception de messages (client ou serveur)

Pour recevoir des messages, utilisez la séquence suivante.

WsCreateMessageForChannel
for each message being received
{
    WsReceiveMessage            // receive a message
    WsGetHeader*                // optionally access standard headers such as To or Action
    WsResetMessage              // reset if reading another message
}
WsFreeMessage

La fonction WsReceiveMessage n’autorise pas la diffusion en continu et suppose que le corps contient un seul élément et que le type du message (action et schéma du corps) est connu à l’avance. Pour éviter ces contraintes, utilisez la séquence suivante au lieu de WsReceiveMessage.

WsReadMessageStart              // read all headers into header buffer
for each standard header
{
    WsGetHeader                 // deserialize standard header such as To or Action
}
for each application defined header
{
    WsGetCustomHeader           // deserialize application defined header
}
for each element of the body
{
    WsFillBody?                 // optionally fill the body
    WsReadBody                  // deserialize element of body
}
WsReadMessageEnd                // read end of message

La fonction WsReadBody utilise la sérialisation pour lire les éléments du corps. Pour lire les données directement à partir du lecteur XML, utilisez la séquence suivante au lieu de WsReadBody.

WS_MESSAGE_PROPERTY_BODY_READER     // get the reader used to read the body
WsFillBody?                         // optionally fill the body
WsReadToStartElement                // read up to the body element
WsReadStartElement                  // consume the start of the body element
// use the read functions to read the contents of the body element
WsReadEndElement                    // consume the end of the body element

Les fonctions WsGetCustomHeader utilisent la sérialisation pour obtenir les en-têtes à partir de la mémoire tampon d’en-tête du message. Pour utiliser le lecteur XML pour lire un en-tête, utilisez la séquence suivante au lieu de WsGetCustomHeader.

WS_MESSAGE_PROPERTY_HEADER_BUFFER   // get the header buffer 
WsCreateReader                      // create an xml reader
WsSetInputToBuffer                  // specify input of reader should be buffer
WsMoveReader*                       // move to inside header element
while looking for header to read
{
    WsReadToStartElement            // see if the header matches the application header
    if header matched
    {
        WsGetHeaderAttributes?      // get the standard header attributes
        WsReadStartElement          // consume the start of the header element
        // use the read functions to read the contents of the header element
        WsReadEndElement            // consume the end of the header element
    }
    else
    {
        WsSkipNode                  // skip the header element
    }
}                

Demande de réponse (client)

L’exécution d’une requête-réponse sur le client peut être effectuée avec la séquence suivante.

WsCreateMessageForChannel               // create request message 
WsCreateMessageForChannel               // create reply message 
for each request reply
{
    WsRequestReply                      // send request, receive reply
    WsResetMessage?                     // reset request message (if repeating)
    WsResetMessage?                     // reset reply message (if repeating)
}
WsFreeMessage                           // free request message
WsFreeMessage                           // free reply message

La fonction WsRequestReply suppose un élément unique pour le corps des messages de demande et de réponse, et que le type du message (action et schéma du corps) est connu à l’avance. Pour éviter ces limitations, la demande et le message de réponse peuvent être envoyés manuellement, comme illustré dans la séquence suivante. Cette séquence correspond à la séquence précédente pour l’envoi et la réception d’un message, sauf indication contraire.

WsInitializeMessage     // initialize message to WS_BLANK_MESSAGE
WsSetHeader             // serialize action header into header buffer
WsAddressMessage?       // optionally address message

// the following block is specific to sending a request
{
    generate a unique MessageID for request
    WsSetHeader         // set the message ID            
}

for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

WsReadMessageStart      // read all headers into header buffer

// the following is specific to receiving a reply
{
    WsGetHeader         // deserialize RelatesTo ID of reply
    verify request MessageID is equal to RelatesTo ID
}

for each standard header
{
    WsGetHeader         // deserialize standard header such as To or Action
}
for each application defined header
{
    WsGetCustomHeader   // deserialize application defined header
}
for each element of the body
{
    WsFillBody?         // optionally fill the body
    WsReadBody          // deserialize element of body
}
WsReadMessageEnd        // read end of message                

Demande de réponse (serveur)

Pour recevoir un message de demande sur le serveur, utilisez la même séquence que celle décrite dans la section précédente sur la réception des messages.

Pour envoyer une réponse ou un message d’erreur, utilisez la séquence suivante.

WsCreateMessageForChannel
for each reply being sent
{
    WsSendReplyMessage | WsSendFaultMessageForError  // send reply or fault message
    WsResetMessage?     // reset if sending another message
}
WsFreeMessage

La fonction WsSendReplyMessage suppose un seul élément dans le corps et n’autorise pas la diffusion en continu. Pour éviter ces limitations, utilisez la séquence suivante. Il s’agit de la même séquence que la séquence précédente pour l’envoi d’un message, mais utilise WS_REPLY_MESSAGE au lieu de WS_BLANK_MESSAGE lors de l’initialisation.

// the following block is specific to sending a reply
{
    WsInitializeMessage // initialize message to WS_REPLY_MESSAGE
}
WsSetHeader             // serialize action header into header buffer                                
WsAddressMessage?       // optionally address message
for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

Modèles d’échange de messages

La WS_CHANNEL_TYPE dicte le modèle d’échange de messages possible pour un canal donné. Le type pris en charge varie en fonction de la liaison, comme suit :

Boucles de message

Pour chaque modèle d’échange de messages, il existe une « boucle » spécifique qui peut être utilisée pour envoyer ou recevoir des messages. La boucle décrit l’ordre juridique des opérations nécessaires pour envoyer/recevoir plusieurs messages. Les boucles sont décrites ci-dessous en tant que productions grammaticales. Le terme « end » est une réception dans laquelle WS_S_END est retourné (voir Valeurs de retour des services Web Windows), indiquant qu’aucun autre message n’est disponible sur le canal. La production parallèle spécifie que pour parallel(x & y), l’opération x peut être effectuée simultanément avec y.

Les boucles suivantes sont utilisées sur le client :

client-loop := client-request-loop | client-duplex-session-loop | client-duplex-loop
client-request-loop := open (send (receive | end))* close // WS_CHANNEL_TYPE_REQUEST
client-duplex-session-loop := open parallel(send* & receive*) parallel(send? & end*) close // WS_CHANNEL_TYPE_DUPLEX_SESSION
client-duplex-loop := open parallel(send & receive)* close // WS_CHANNEL_TYPE_DUPLEX

Les boucles suivantes sont utilisées sur le serveur :

server-loop: server-reply-loop | server-duplex-session-loop | server-duplex-loop
server-reply-loop := accept receive end* send? end* close // WS_CHANNEL_TYPE_REPLY
server-duplex-session-loop := accept parallel(send* & receive*) parallel(send* & end*) close // WS_CHANNEL_TYPE_DUPLEX_SESSION
server-input-loop := accept receive end* close // WS_CHANNEL_TYPE_INPUT

L’utilisation de la WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING sur le serveur nécessite une réception réussie avant que l’envoi soit autorisé même avec un canal de type WS_CHANNEL_TYPE_DUPLEX_SESSION. Après la première réception. la boucle régulière s’applique.

Notez que les canaux de type WS_CHANNEL_TYPE_REQUEST et WS_CHANNEL_TYPE_REPLY peuvent être utilisés pour envoyer et recevoir des messages unidirectionnel (ainsi que le modèle demande-réponse standard). Pour ce faire, fermez le canal de réponse sans envoyer de réponse. Dans ce cas, aucune réponse n’est reçue sur le canal de demande. La valeur de retour WS_S_END l’utilisation du WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING sur le serveur nécessite une réception réussie avant que l’envoi soit autorisé même avec un canal de type WS_CHANNEL_TYPE_DUPLEX_SESSION. Après la première réception, la boucle régulière s’applique.

sera retourné, indiquant qu’aucun message n’est disponible.

Les boucles client ou serveur peuvent être effectuées en parallèle les unes avec les autres à l’aide de plusieurs instances de canal.

parallel-client: parallel(client-loop(channel1) & client-loop(channel2) & ...)
parallel-server: parallel(server-loop(channel1) & server-loop(channel2) & ...)

Filtrage des messages

Un canal serveur peut filtrer les messages reçus qui ne sont pas destinés à l’application, tels que les messages qui établissent un contexte de sécurité. Dans ce cas , WS_S_END seront retournés à partir de WsReadMessageStart et aucun message d’application ne sera disponible sur ce canal. Toutefois, cela ne signale pas que le client a l’intention de mettre fin à la communication avec le serveur. D’autres messages peuvent être disponibles sur un autre canal. Consultez WsShutdownSessionChannel.

Annulation

La fonction WsAbortChannel est utilisée pour annuler les E/S en attente d’un canal. Cette API n’attend pas que la ou les opérations d’E/S se terminent. Pour plus d’informations, consultez le diagramme d’état WS_CHANNEL_STATE et la documentation de WsAbortChannel .

L’API WsAbortListener est utilisée pour annuler les E/S en attente d’un écouteur. Cette API n’attend pas que la ou les opérations d’E/S se terminent. L’abandon d’un écouteur entraîne également l’abandon des acceptations en attente. Pour plus d’informations, consultez le diagramme d’état WS_LISTENER_STATE et WsAbortListener .

TCP

Le WS_TCP_CHANNEL_BINDING prend en charge SOAP sur TCP. La spécification SOAP sur TCP s’appuie sur le mécanisme d’encadrement .NET.

Le partage de ports n’est pas pris en charge dans cette version. Chaque écouteur ouvert doit utiliser un numéro de port différent.

UDP

Le WS_UDP_CHANNEL_BINDING prend en charge SOAP sur UDP.

La liaison UDP présente un certain nombre de limitations :

  • La sécurité n’est pas prise en charge.
  • Les messages peuvent être perdus ou dupliqués.
  • Un seul encodage est pris en charge : WS_ENCODING_XML_UTF8.
  • Les messages sont fondamentalement limités à 64 000 et ont souvent une plus grande chance d’être perdus si la taille dépasse la MTU du réseau.

HTTP

Le WS_HTTP_CHANNEL_BINDING prend en charge SOAP sur HTTP.

Pour contrôler les en-têtes spécifiques HTTP sur le client et le serveur, consultez WS_HTTP_MESSAGE_MAPPING.

Pour envoyer et recevoir des messages non SOAP sur le serveur, utilisez WS_ENCODING_RAW pour WS_CHANNEL_PROPERTY_ENCODING.

NAMEDPIPES

Le WS_NAMEDPIPE_CHANNEL_BINDING prend en charge SOAP sur les canaux nommés, ce qui permet la communication avec le service Windows Communication Foundation (WCF) à l’aide de NetNamedPipeBinding.

Corrélation des messages de demande/réponse

Les messages de demande/réponse sont corrélés de l’une des deux façons suivantes :

  • La corrélation est effectuée à l’aide du canal comme mécanisme de corrélation. Par exemple, lorsque vous utilisez WS_ADDRESSING_VERSION_TRANSPORT et WS_HTTP_CHANNEL_BINDING la réponse pour le message de demande est corrélée à la demande par le fait qu’il s’agit du corps de l’entité de la réponse HTTP.
  • La corrélation est effectuée à l’aide des en-têtes MessageID et RelatesTo. Ce mécanisme est utilisé avec WS_ADDRESSING_VERSION_1_0 et WS_ADDRESSING_VERSION_0_9 (même lors de l’utilisation de WS_HTTP_CHANNEL_BINDING). Dans ce cas, le message de demande inclut l’en-tête MessageID. Le message de réponse inclut un en-tête RelatesTo qui a la valeur de l’en-tête MessageID de la requête. L’en-tête RelatesTo permet au client de mettre en corrélation une réponse avec une demande qu’il a envoyée.

Les API de couche de canal suivantes utilisent automatiquement les mécanismes de corrélation appropriés en fonction de la WS_ADDRESSING_VERSION du canal.

Si ces API ne sont pas utilisées, les en-têtes peuvent être ajoutés manuellement et accessibles à l’aide de WsSetHeader ou WsGetHeader.

Canaux et écouteurs personnalisés

Si l’ensemble prédéfini de liaisons de canal ne répond pas aux besoins de l’application, une implémentation de canal et d’écouteur personnalisé peut être définie en spécifiant WS_CUSTOM_CHANNEL_BINDING lors de la création du canal ou de l’écouteur. L’implémentation réelle du canal/écouteur est spécifiée sous la forme d’un ensemble de rappels via des propriétés WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS ou WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS . Une fois qu’un canal ou un écouteur personnalisé est créé, le résultat est un objet WS_CHANNEL ou WS_LISTENER qui peut être utilisé avec les API existantes.

Un canal et un écouteur personnalisés peuvent également être utilisés avec le proxy de service et l’hôte de service en spécifiant la valeur WS_CUSTOM_CHANNEL_BINDING dans l’énumération WS_CHANNEL_BINDING et les propriétés WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS et WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS lors de la création du proxy de service ou de l’hôte de service.

Sécurité

Le canal permet de limiter la quantité de mémoire utilisée pour différents aspects des opérations via des propriétés telles que :

Ces propriétés ont des valeurs par défaut qui sont conservatrices et sécurisées pour la plupart des scénarios. Les valeurs par défaut et leurs modifications doivent être soigneusement évaluées par rapport aux vecteurs d’attaque potentiels susceptibles de provoquer un déni de service par un utilisateur distant.

Le canal permet de définir des valeurs de délai d’expiration pour différents aspects des opérations via des propriétés telles que :

Ces propriétés ont des valeurs par défaut qui sont conservatrices et sécurisées pour la plupart des scénarios. L’augmentation des valeurs de délai d’expiration augmente la durée pendant laquelle une partie distante peut maintenir une ressource locale active, telle que la mémoire, les sockets et les threads effectuant des E/S synchrones. Une application doit évaluer les valeurs par défaut et faire preuve de prudence lors de l’augmentation d’un délai d’expiration, car cela peut ouvrir des vecteurs d’attaque potentiels qui peuvent entraîner un déni de service à partir d’un ordinateur distant.

Voici quelques-unes des autres options de configuration et considérations relatives à la conception des applications qui doivent être soigneusement évaluées lors de l’utilisation de l’API de canal WWSAPI :

  • Lors de l’utilisation de la couche canal/écouteur, il incombe à l’application de créer et d’accepter des canaux côté serveur. De même, il incombe à l’application de créer et d’ouvrir des canaux côté client. Une application doit placer une limite supérieure sur ces opérations, car chaque canal consomme de la mémoire et d’autres ressources limitées telles que les sockets. Une application doit être particulièrement prudente lors de la création d’un canal en réponse à toute action déclenchée par un tiers distant.
  • Il appartient à l’application d’écrire la logique pour créer des canaux et les accepter. Chaque canal consomme des ressources limitées telles que la mémoire et les sockets. Une application doit avoir une limite supérieure sur le nombre de canaux qu’elle est prête à accepter, ou un tiers distant peut établir de nombreuses connexions, ce qui entraîne OOM et, par conséquent, un déni de service. Il doit également recevoir activement des messages de ces connexions à l’aide d’un petit délai d’expiration. Si aucun message n’est reçu, l’opération expire et la connexion doit être libérée.
  • Il appartient à une application d’envoyer une réponse ou une erreur en interprétant les en-têtes SOAP ReplyTo ou FaultTo. La pratique sécurisée consiste à respecter uniquement un en-tête ReplyTo ou FaultTo qui est « anonyme », ce qui signifie que la connexion existante (TCP, HTTP) ou l’adresse IP source (UDP) doit être utilisée pour envoyer la réponse SOAP. Les applications doivent faire preuve d’une extrême prudence lors de la création de ressources (par exemple, un canal) afin de répondre à une autre adresse, sauf si le message a été signé par une partie qui peut parler de l’adresse à laquelle la réponse est envoyée.
  • La validation effectuée dans la couche de canal ne remplace pas l’intégrité des données obtenue grâce à la sécurité. Une application doit s’appuyer sur les fonctionnalités de sécurité du WWSAPI pour s’assurer qu’elle communique avec une entité approuvée, et doit également s’appuyer sur la sécurité pour garantir l’intégrité des données.

De même, il existe des options de configuration des messages et des considérations relatives à la conception d’application qui doivent être soigneusement évaluées lors de l’utilisation de l’API de message WWSAPI :

  • La taille du tas utilisé pour stocker les en-têtes d’un message peut être configurée à l’aide de la propriété WS_MESSAGE_PROPERTY_HEAP_PROPERTIES . L’augmentation de cette valeur permet de consommer plus de mémoire par les en-têtes du message, ce qui peut conduire à OOM.
  • L’utilisateur de l’objet message doit se rendre compte que les API d’accès en-tête sont O(n) par rapport au nombre d’en-têtes dans le message, car elles case activée pour les doublons. Les conceptions qui nécessitent de nombreux en-têtes dans un message peuvent entraîner une utilisation excessive du processeur.
  • Le nombre maximal d’en-têtes dans un message peut être configuré à l’aide de la propriété WS_MESSAGE_PROPERTY_MAX_PROCESSED_HEADERS . Il existe également une limite implicite basée sur la taille du tas du message. L’augmentation de ces deux valeurs permet la présence d’en-têtes supplémentaires, ce qui augmente le temps nécessaire à la recherche d’un en-tête (lors de l’utilisation des API d’accès à l’en-tête).