Posted by: Camilo Torres | December 8, 2010

El protocolo de colas de mensajes STOMP

El presente artículo es una explicación basada en el ‘Stomp Protocol Specification, Version 1.0’ publicado por Codehaus en http://stomp.codehaus.org/Protocol.

1. El protocolo STOMP

El protocolo STOMP de colas de mensajes se utiliza para comunicación distribuida entre computadoras, principalmente en aplicaciones de informática. STOMP es el Protocolo de Mensajes Orientado a Texto y en Flujos (Streaming Text Oriented Messaging Protocol). Esto significa que los mensajes que se pueden crear y enviar por un lado, y recibir y procesar por el otro lado son mensajes de texto; puede ser cualquier documento de texto: plano, yaml, json, xml, etc. También significa que los mensajes se envían y reciben dentro de un flujo de datos, dicho de otra manera, mensajes van y mensajes vienen dentro de un mismo contexto de conexión.

Los servicios de colas de mensajes son utilizados desde hace mucho tiempo en aplicaciones dentro de bancos, empresas de telecomunicaciones y algunas otras industrias. Tienen mucha aplicación en la informática porque permiten una forma de computación distribuida con bajo acoplamiento, además es fácil de entender porque presenta un patrón de tipo productor-consumidor. Un caso fácil de entender sacado de morethanseven es cuando una web solicita información al cliente y cuando el cliente pulsa el botón de enviar la web le envía un correo electrónico, digamos con un resumen de su petición; si llegan muchos usuarios a enviar el formulario, el servidor se pondrá lento enviando correos, mientras que los usuarios irán notando que la página se vuelve lenta; esto ocurre porque el envío de correo toma un poco de tiempo y el servidor de web es quien está enviando cada correo (con la ayuda del servidor de correos).

Servidor de Colas

Servidor de Colas

Ahora, si en vez de que el servidor web envíe el mensaje directamente al servidor de correo, lo coloca en una cola, esta cola a su vez está en un servidor aparte, y luego de dejar el mensaje en cola el servidor web muestra una respuesta al usuario liberandose para los demás usuarios que envíen el formulario. Esto haría que el servidor web no se ponga lento tan rápido. Pero ¿qué pasa con el mensaje? aún no se ha enviado por correo. Es cierto, el mensaje está en una cola en un servidor de colas, pero no se queda allí, porque la magia del servidor de colas es que este despacha el mensaje a un servicio que está procesando los mensajes de la cola, y ese servicio si va a enviar el correo: toma el mensaje de la cola y lo manda por correo; si llegan muchos mensajes, no importa, el servidor de colas de mensajes los va agregando al final de la cola y va despachando uno a uno según el proceso que los consume los pueda ir tomando y enviando sin saturar los recursos ni del servidor de correos y menos del servidor web.

Entonces el sistema es muy sencillo, un proceso escribe mensajes en el servidor, el servidor los mete en una cola y los va despachando hacia otro proceso que los va consumiendo según tenga disponibilidad de recursos.

Existen otros protocolos de mensajes como XMPP, SMTP o JMS para nombrar solo algunos y dejar por fuera mucho más. STOMP es un protocolo sencillo y ligero, por lo tanto está condenado al éxito y a ser usado en muchas aplicaciones que requieran sencillez y facilidad de uso. A partir de este punto se va a explicar el funcionamiento del protocolo STOMP basado en la propia especificación del protocolo.

2. El cliente se conecta con el servidor STOMP.

Lo primero que debe hacer el cliente es abrir un socket al servidor. Es conveniente asumir que la conexión será con TCP ya que es lo más común y los servidores están hechos así. Para conectarse, un cliente debe enviar la siguiente trama:

CONNECT
login:usuario
passcode:clavesecreta

^@

El símbolo ^@ es un byte null (control-@ en ASCII). Se usa para definir el fin de mensaje. Toda trama inicia con un comando (en este caso CONNECT que significa conectar) en una línea, siguen los encabezados en forma ‘clave:valor’ cada uno en su propia línea (login y passcode en este ejemplo), se deja una línea en blanco y luego viene el cuerpo del mensaje, el cual termina con el byte ^@ (en este caso el cuerpo del mensaje está en blanco). El encabezado ‘login’ (entrar) se usa para enviar el usuario que se conecta, mientras que passcode (código de pase) se usa para enviar la clave secreta. Para separar las líneas se usa un caracter de nueva línea al final de cada línea.

Luego que el cliente envía la trama CONNECT el servidor responde con la siguiente trama:

CONNECTED
session:identificador de sesión

^@

CONNECTED significa conectado. El encabezado session es un identificador que puede ser cualquier texto con tal que sea distinto para cada conexión que se haga con el servidor (la especificación lo manda, pero no es usado para nada actualmente; en el futuro puede ser usado en otras tramas).

Ya con esto se establece una conexión al servidor y se pueden realizar varias acciones, cada una con su trama:

  • SEND – Enviar. Enviar mensajes a una cola en el servidor.
  • SUBSCRIBE – Suscribirse. Suscribirse para recibir mensajes que otros pongan en una cola.
  • UNSUBSCRIBE – Desuscribirse. Dejar de recibir mensajes que otros pongan en una cola.
  • BEGIN – Iniciar. Inicia una transacción.
  • COMMIT – Comprometer. Compromete (ejecuta definitivamente) las acciones que se hicieron en una transacción.
  • ABORT – Abortar. Deshacer las acciones hechas en una transacción.
  • ACK – Confirmación (Acknowledge). Confirma la recepción de un mensaje por parte del proceso que lo consume.
  • DISCONNECT – Desconectar. Para terminar una conexión con el servidor.

Entonces lo primero es conectarse, luego se pueden enviar o consumir muchos mensajes, dependiendo del lado donde esté el proceso (en el lado que pone mensajes o en el lado que los consume) y finalmente en algún momento se cierra la conexión. Esta es la parte de flujos del protocolo, como se puede ver todo ocurre dentro de un flujo de mensajes que van y vienen entre el servidor y los clientes que ponen mensajes y que consumen mensajes.

3. Comandos que pueden enviar los clientes.

SEND.

Significa enviar. Sirve para enviar o colocar mensajes hacia un destino en el servidor. Este lo usa el cliente que quiere poner un mensaje en una cola. Tiene un encabezado requerido: destination (destino) que indica donde se va a enviar el mensaje. El cuerpo de la trama SEND es el mensaje que se quiere enviar. Ejemplo:

SEND
destination:/cola/mensajes

Texto del mensaje puesto en la cola
^@

Esto envía el mensaje ‘Texto del mensaje puesto en la cola’ a la cola llamada ‘/cola/mensajes’. El nombre de la cola es totalmente arbitrario; en este ejemplo se usó la palabra ‘cola’ pero en el servidor no necesariamente es una cola, puede ser otra cosa que el servidor quiera implementar, no hay nada que obligue al servidor a usar una cola. Los nombres en ‘destination’ (destino) únicamente identifican un lugar en el servidor para poner los mensajes, cada nombre indica un lugar distinto, cada servidor puede implementar este mecanismo como quiera.

Las tramas SEND soportan un encabezado ‘transaction’ (transacción) que sirve para enviar el mensaje en el contexto de una transacción.

Se recomienda que las tramas SEND incluyan un encabezado ‘content-length’ (longitud del contenido) que es el número de bytes del cuerpo del mensaje. Esto sirve cuando el mensaje incluye caracteres de null (^@); si no se incluye un content-length el servidor tomará el mensaje hasta el primer null que encuentre e ignorará el resto del mensaje; por el contrario si tiene content-length el servidor leerá esa cantidad de bytes del mensaje, incluyendo los bytes que sean null (^@).

SUBSCRIBE

Significa suscribir. Se usa para registrar que se quieren consumir mensajes de un destino (‘destination’) dentro del servidor; por ejemplo consumir mensajes de una cola. Al igual que la trama SEND, la trama SUBSCRIBE requiere un encabezado ‘destination’ (destino) para indicar la cola a la que se quiere suscribir. De esa forma, cualquier mensaje que se reciba en la cola (porque otro proceso puso el mensaje) será enviado desde el servidor al proceso cliente que se suscribió a la cola como una trama MESSAGE (mensaje). Se puede colocar un encabezado ‘ack’ (confirmar, acknowledge), pero es opcional y si no se coloca el valor es ‘auto’. Ejemplo:

SUBSCRIBE
destination:/cola/mensajes
ack:client

^@

En este ejemplo el encabezado ‘ack’ (confirmar) está en ‘client’ (cliente) lo que significa que el mensaje será considerado como enviado al proceso cliente cuando el proceso cliente envíe al servidor una trama ACK (confirmación). Los valores válidos para el encabezado ‘ack’ son ‘auto’ (automático, que es el que se asume por defecto si no se manda encabezado ‘ack’) y ‘client’ (cliente).

El cuerpo de las tramas SUBSCRIBE (suscribir) es ignorado completamente.

Se puede agregar un encabezado ‘id’ (identificador) que podrá ser usado luego en una trama UNSUBSCRIBE (desuscribir), de esa forma, si un proceso tiene varias suscripciones a un mismo ‘destination’ (destino), puede elegir a cual de-suscribirse porque le asignó distinto ‘id’ (identificador) a cada proceso. Si se indica un encabezado ‘id’ entonces el servidor deberá agregar a su vez un encabezado ‘subscription’ (suscripción) a las tramas MESSAGE (mensaje) que él envía al cliente suscrito, de forma que el cliente podrá saber exactamente a cual suscripción se refiere la trama MESSAGE (mensaje). Si el servidor soporta comodines (wildcards) y selectores (selectors) el ‘id’ puede servir a los clientes de ayuda para saber cual suscripción originó el mensaje. Los comodines y selectores se explican más abajo. El servidor podría soportar un encabezado ‘selector’ que puede contener un selector de tipo SQL 92 como se explica hacia el final del artículo.

UNSUBSCRIBE

Significa de-suscribir. Esta trama se utiliza para remover una suscripción existente de forma que ya no se reciban más mensajes provenientes del destino (destination) al que previamente se suscribió. Requiere uno de estos dos encabezados: ‘destination’ (destino) o ‘id’ (identificador). Para usar el encabezado ‘id’ tuvo que haberse pasado también al momento de hacer la suscripción. Ejemplo:

UNSUBSCRIBE
destination:/cola/mensajes

^@

BEGIN

Significa iniciar. Se usa para iniciar una transacción. Las transacciones en este protocolo solo aplican a envío de mensajes y confirmación de recepción de mensajes. Cualquier mensaje enviado o al que se le confirme su recepción será manejado atómicamente durante la transacción. Ejemplo:

BEGIN
transaction:identificador

^@

El encabezado ‘transaction’ (transacción) es requerido y se refiere a un identificador que deberá ser usado en las tramas SEND, COMMIT, ABORT y ACK para indicar que están asociadas a la transacción por el nombre dado en el identificador.

COMMIT

Significa comprometer. Se usa para ejecutar definitivamente una transacción en curso, es decir, para indicar que la transacción terminó exitosamente y deben ejecutarse atómicamente todos comandos enviados desde el BEGIN. Ejemplo:

COMMIT
transaction:identificador

^@

El encabezado ‘transaction’ (transacción) es requerido y se refiere al identificador que se indicó al iniciar la transacción con la trama BEGIN.

ACK

Significa confirmar (acknowledge). Se utiliza para confirmar la recepción y consumo de un mensaje de parte del cliente, cuando el tipo de confirmación es ‘client’ (cliente). Cuando un cliente envía una trama SUBSCRIBE (suscribir) con un encabezado ‘ack’ (confirmación, acknowledge) con valor ‘client’ (cliente) entonces para que el servidor considere que el cliente ha consumido el mensaje, el cliente debe enviar una trama ACK de confirmación.

ACK tiene el encabezado ‘message-id’ (identificador del mensaje) como requerido, y el identificador debe coincidir con el que envía el servidor en la trama MESSAGE (mensaje) correspondiente para ser confirmada. Adicionalmente un encabezado ‘transaction’ (transacción) puede ser usado para indicar que la trama está dentro de una transacción. Ejemplo:

ACK
message-id:identificador
transaction:numero de transacción

^@

El encabezado ‘transaction’ es opcional.

ABORT

Significa Abortar. Se usa para indicar que se debe abortar o rechazar una transacción en curso. Es decir, que ningún mensaje dentro de la transacción debe ser enviado. Ejemplo:

ABORT
transaction:numero de transacción

^@

El encabezado ‘transaction’ (transacción) es requerido para identificar la transacción.

DISCONNECT

Significa desconectar. Se usa para desconectarse limpiamente del servidor. Es adecuado hacer una desconexión antes de cerrar el socket TCP. Ejemplo:

DISCONNECT

^@

4. Encabezados estándar.

Algunos encabezados pueden ser usados en la mayoría de las tramas y tienen un significado especial.

receipt

Significa recibo. Cualquier trama que envíe el cliente menos CONNECT puede hacer uso del encabezado ‘receipt’ con un valor arbitrario. Esto hará que el servidor confirme la recepción de la trama enviando al cliente una trama RECEIPT que contendrá el valor del este encabezado en el encabezado ‘receipt-id’ de la trama RECEIPT. Ejemplo:

SEND
destination:/cola/mensajes
receipt:mens876

Mensaje con confirmación de recibo.
^@

5. Tramas del servidor

El servidor enviará también tramas al cliente, adicional a la trama CONNECTED (conectado) que envía al momento de la conexión puede enviar también:

  • MESSAGE
  • RECEIPT
  • ERROR

MESSAGE

Significa mensaje. Estas tramas se usan para enviar los mensajes de las suscripciones a los clientes suscritos. Las tramas MESSAGE contienen un encabezado ‘destination’ (destino) indicando la cola destino que recibió el mensaje. También contendrá un encabezado ‘message-id’ (identificador del mensaje) con un identificador único. El cuerpo de la trama contiene el mensaje. Ejemplo:

MESSAGE
destination:/cola/de/ejemplo
message-id:identidad9001

Este es el mensaje^@

Se recomienda que las tramas MESSAGE incluyan un encabezado ‘content-lenght’ (longitud del contenido) con una cuenta de los bytes del cuerpo de la trama. Si viene el encabezado ‘content-length’ entonces se leerán esa cantidad de bytes. Esto permite tener caracteres null en el cuerpo del mensaje.

RECEIPT

Significa recibo. El servidor envía tramas de recepción cuando el cliente ha especificado un encabezado ‘receipt’ en algún comando. La trama RECEIPT contiene un encabezado ‘receipt-id’ que contiene el mismo que vino en el encabezado ‘receipt’ que envió el cliente. Ejemplo:

RECEIPT
receipt-id:mens876

^@

El cuerpo del mensaje debe estar vacío.

ERROR

El servidor podría enviar tramas ERROR si algo va mal. Las tramas de error contendrán un encabezado ‘message’ (mensaje) con una breve descripción del error, el cuerpo podrá contener todos los detalles del error o venir vacío. Ejemplo:

ERROR
message:trama no reconocida

El mensaje:
-----------
COLA
destination:colademensajes

Datos del mensaje
-----------
no contiene un comando válido del protocolo STOMP.
^@

Se recomienda que las tramas ERROR incluyan un encabezado ‘content-lenght’ (longitud del contenido) con una cuenta de los bytes del cuerpo de la trama. Si viene el encabezado ‘content-length’ entonces se leerán esa cantidad de bytes. Esto permite tener caracteres null en el cuerpo del error.

6. Comodines (wildcards)

Esta sección es una explicación basada en la página de Apache ActiveMQ sobre Wildcards de The Apache Software Foundation ubicada en http://activemq.apache.org/wildcards.html.

El soporte de comodines sirve para suscribirse en un solo comando SUBSCRIBE a varias colas. Un ejemplo clásico de esto es cuando existen varias colas cada una de las cuales lleva el control de los cambios de precios de una acción en particular, entonces se tendrían varias colas como por ejemplo:

  1. bolsavalores/rentavariable/MPA
  2. bolsavalores/rentavariable/CRM.A
  3. bolsavalores/rentavariable/MVZ.A
  4. bolsavalores/rentavariable/MVZ.B

Si se quiere que una sola suscripción consuma todas las colas de acciones clase A (CRM.A y MVZ.A) entonces puede suscribirse con el siguiente comodín:

SUBSCRIBE
destination:bolsavalores/rentavariable/*.A

^@

Se observa que el * significa que puede haber cualquier texto en ese lugar.

En otro ejemplo un proceso quiere suscribirse para consumir todas las colas de renta variable, puede hacer así:

SUBSCRIBE
destination:bolsavalores/rentavariable/>

^@

En este caso el símbolo > (mayor que) significa que puede haber cualquier texto de allí en adelante.

Entonces:

  • El símbolo * singnifica que puede coincidir con cualquier texto. Se usa para coincidir un trozo del nombre de la cola.
  • El símbolo > significa que puede coincidir cualquier texto que siga a partir de allí. Se usa para coincidir todas las colas en una jerarquía.

7. Selectores

Esta sección es una explicación basada en la página de Apache ActiveMQ sobre Selectors de The Apache Software Foundation ubicada en http://activemq.apache.org/selectors.html.

Los selectores se utilizan para recibir únicamente algunos mensajes de una suscripción y no todos. Normalmente se reciben todos los mensajes que llegan a una cola a la que se está suscrito, pero si se usan selectores se podrían filtrar algunos mensajes.

Los selectores utilizan sintaxis de SQL 92 y solo se pueden usar claves del encabezado, por ejemplo:

SUBSCRIBE
destination:/mi/cola/con/muchosMensajes
selector:id = 'cola080' and ack='client'

^@

Los selectores no pueden usarse con el cuerpo del mensaje, solo con los encabezados.

8. Algunos enlaces

Copyright 2010. Camilo Torres.

Licencia  Creative Commons
Este obra está bajo una licencia Creative Commons Reconocimiento 3.0 Unported.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: