Vue d’ensemble de la couche canal

La couche 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 à partir de structures SOAP. La couche canal permet un contrôle total des communications par le biais de messages composés de données envoyées ou reçues et contenant des corps et des en-têtes, et des canaux qui abstraitent les protocoles d’échange de messages et fournissent des propriétés pour personnaliser les paramètres.

Message

Un message est un objet qui encapsule les données réseau , en particulier les données transmises ou reçues sur un réseau. La structure de 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.

Diagram showing the header and body of a 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 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.

Diagram showing several message encoding formats.

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 lorsqu’il est envoyé. L’envoi d’un message sur un canal ressemble à le placer dans une chute : le canal inclut les informations sur lesquelles le message doit aller et comment y parvenir.

Diagram showing channels for messages.

Les canaux sont classés en types de canaux. Un type de canal spécifie les messages de direction qui peuvent circuler. Le type de canal identifie également si le canal est sessionful ou sans session. Une session est définie comme un moyen abstrait de mettre en corrélation les messages entre deux ou plusieurs parties. Un exemple de canal avec session est un canal TCP, qui utilise la connexion TCP comme implémentation de session concrète. Un exemple de canal sans session est UDP, 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.

Diagram showing sessionful and sessionless channel types.

Bien que les types de canal décrivent les informations de direction et de session d’un canal, ils ne spécifient pas la façon dont le canal est implémenté. Quel protocole le canal doit-il utiliser ? Comment le canal doit-il essayer de remettre le message ? Quel type de sécurité est utilisé ? Est-ce qu’il est monodiffusion ou multidiffusion ? Ces paramètres sont appelés « liaison » du canal. La liaison se compose des éléments suivants :

Diagram showing a list of channel properties.

Écouteur

Pour commencer à communiquer, le client crée un objet Channel. Mais comment le service obtient-il son objet Channel ? Il le fait en créant un écouteur. La création d’un écouteur nécessite les mêmes informations de liaison nécessaires pour créer un canal. Une fois qu’un écouteur a été créé, l’application peut accepter les canaux à partir de l’écouteur. Étant donné que l’application peut se trouver derrière les canaux d’acceptation, les écouteurs conservent généralement une file d’attente de canaux prêts à accepter (jusqu’à un quota).

Diagram showing channels in the Listener queue.

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

Accepter 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 pour é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 ne contient qu’un seul élément et que le type du message (action et schéma du corps) est connu avant. 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 réponse à une demande 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 qu’un seul élément pour le corps des messages de requête et de réponse, et que le type du message (action et schéma du corps) est connu à l’avant-plan. Pour éviter ces limitations, le message de demande et de réponse peut être envoyé 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, à l’exception de l’emplacement indiqué.

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 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 selon 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 de grammaire. Le terme « fin » est une réception dans laquelle WS_S_END est retourné (Voir Windows valeurs de retour des services web), indiquant qu’aucun autre message n’est disponible sur le canal. La production parallèle spécifie que pour parallel(x y) que 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 l’envoi est autorisée 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 unidirectionnels (ainsi que le modèle standard de demande-réponse). Cela s’effectue en fermant 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 de l’WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING sur le serveur nécessite une réception réussie avant l’envoi est autorisée même avec un canal de type WS_CHANNEL_TYPE_DUPLEX_SESSION. Après la première réception de la boucle régulière s’applique.

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

Les boucles client ou serveur peuvent être effectuées en parallèle 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 de 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 par WsReadMessageStart et aucun message d’application n’est 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. Voir WsShutdownSessionChannel.

Annulation

La fonction WsAbortChannel est utilisée pour annuler les E/S en attente pour un canal. Cette API n’attend pas que 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 pour un écouteur. Cette API n’attend pas que 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.

Il existe plusieurs limitations avec la liaison UDP :

  • Il n’existe aucune prise en charge de la sécurité.
  • 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 le 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 des 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 manières 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 requête est corrélée à la requête en l’état, c’est-à-dire le 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 et accessibles manuellement à 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 WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS ou WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS propriétés. Une fois qu’un canal ou un écouteur personnalisé sont créés, le résultat est un objet WS_CHANNEL ou WS_LISTENER qui peut être utilisé avec des 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 toutes les modifications à ces derniers 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 contenir une ressource locale active, telle que la mémoire, les sockets et les threads qui effectuent des E/S synchrones. Une application doit évaluer les valeurs par défaut et utiliser la prudence lors de l’augmentation d’un délai d’expiration, car elle peut ouvrir des vecteurs d’attaque potentiels susceptibles de provoquer un déni de service à partir d’un ordinateur distant.

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

  • Lorsque vous utilisez 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 prudent lors de la création d’un canal en réponse à toute action déclenchée par un tiers distant.
  • Il incombe à l’application d’écrire la logique pour créer des canaux et de 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 disposé à accepter, ou une partie distante peut établir de nombreuses connexions, ce qui conduit à OOM et donc au déni de service. Il doit également recevoir activement des messages de ces connexions à l’aide d’un délai d’expiration réduit. Si aucun message n’est reçu, l’opération expire et la connexion doit être libérée.
  • Il incombe à 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 uniquement à respecter 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 un tiers qui peut parler de l’adresse à laquelle la réponse est envoyée.
  • La validation effectuée dans la couche de canal n’est pas un remplacement de l’intégrité des données obtenue par le biais de 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 de message et des considérations de conception d’application qui doivent être soigneusement évaluées lors de l’utilisation de l’API de message WWSAPI :

  • La taille du tas utilisée 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 d’utiliser plus de mémoire par les en-têtes du message, ce qui peut entraîner OOM.
  • L’utilisateur de l’objet de message doit se rendre compte que les API d’accès aux en-têtes sont O(n) par rapport au nombre d’en-têtes du message, car ils recherchent des 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 de présenter davantage d’en-têtes, ce qui complique le temps nécessaire à la recherche d’un en-tête (lors de l’utilisation des API d’accès à l’en-tête).