Zend_Feed_Pubsubhubbub es una implementación de la Especificación
Core 0.2 de PubSubHubbub (Working Draft). Ofrece implementaciones de un Publicador y un
Suscriptor Pubsubhubbub adecuadas para Zend Framework y otras aplicaciones
PHP.
Pubsubhubbub es un protocolo pubsub abierto, simple y de escala web. Un caso de uso habitual es permitir que los blogs (Publicadores) "empujen" actualizaciones desde sus fuentes RSS o Atom (Temas) hacia los Suscriptores finales. Estos Suscriptores se habrán suscrito a la fuente RSS o Atom del blog a través de un Hub, un servidor central al que el Publicador notifica cualquier actualización y que luego distribuye estas actualizaciones a todos los Suscriptores. Cualquier fuente puede anunciar que soporta uno o más Hubs mediante un elemento link con espacio de nombres Atom y un atributo rel de "hub".
Pubsubhubbub ha atraído atención porque es un protocolo pubsub fácil de implementar y que opera sobre HTTP. Su filosofía consiste en reemplazar el modelo tradicional en el que las fuentes de los blogs se sondeaban (polling) a intervalos regulares para detectar y recuperar actualizaciones. Dependiendo de la frecuencia del sondeo, esto puede tardar mucho tiempo en propagar las actualizaciones a las partes interesadas, desde agregadores planetarios hasta lectores de escritorio. Con un sistema pubsub implementado, las actualizaciones no son simplemente sondeadas por los Suscriptores, sino que se les empujan (push), eliminando cualquier retraso. Por esta razón, Pubsubhubbub forma parte de lo que se ha denominado la web en tiempo real.
El protocolo no existe de forma aislada. Los sistemas pubsub llevan ya un tiempo existiendo, como el conocido protocolo Jabber Publish-Subscribe, XEP-0060, o el menos conocido rssCloud (descrito en 2001). Sin embargo, estos no han logrado una adopción generalizada, típicamente debido a su complejidad, a un momento de aparición inoportuno o a su falta de idoneidad para aplicaciones web. rssCloud, que fue recientemente revivido como respuesta a la aparición de Pubsubhubbub, también ha visto aumentar su uso de forma significativa, aunque carece de una especificación formal y actualmente no soporta fuentes Atom 1.0.
Quizás sorprendentemente dada su corta edad relativa, Pubsubhubbub ya está en uso, incluyendo Google Reader, Feedburner, y existen plugins disponibles para blogs de Wordpress.
Zend_Feed_Pubsubhubbub implementa dos lados de la Especificación
0.2 de Pubsubhubbub: un Publicador y un Suscriptor. Actualmente no implementa un
Servidor Hub, aunque esto está en desarrollo para una futura versión de Zend Framework.
Un Publicador es responsable de notificar a todos los Hubs soportados (se pueden soportar varios para añadir redundancia al sistema) sobre cualquier actualización de sus fuentes, ya sean basadas en Atom o RSS. Esto se logra haciendo ping a los Servidores Hub soportados con la URL de la fuente actualizada. En la terminología de Pubsubhubbub, cualquier recurso actualizable que pueda ser suscrito se denomina Tema (Topic). Una vez recibido un ping, el Hub solicitará la fuente actualizada, la procesará en busca de elementos actualizados y reenviará todas las actualizaciones a todos los Suscriptores suscritos a esa fuente.
Un Suscriptor es cualquier parte o aplicación que se suscribe a uno o más Hubs para recibir actualizaciones de un Tema alojado por un Publicador. El Suscriptor nunca se comunica directamente con el Publicador, ya que el Hub actúa como intermediario, aceptando suscripciones y enviando actualizaciones a los Suscriptores suscritos. Por lo tanto, el Suscriptor se comunica únicamente con el Hub, ya sea para suscribirse o cancelar la suscripción a Temas, o cuando recibe actualizaciones del Hub. Este diseño de comunicación ("Fat Pings") elimina eficazmente la posibilidad de un problema de "Estampida" (Thundering Herd). Esto ocurre en un sistema pubsub en el que el Hub simplemente informa a los Suscriptores de que hay una actualización disponible, lo que hace que todos los Suscriptores recuperen inmediatamente la fuente del Publicador, dando lugar a un pico de tráfico. En Pubsubhubbub, el Hub distribuye la actualización real en un "Fat Ping", de modo que el Publicador no queda sometido a ningún pico de tráfico.
Zend_Feed_Pubsubhubbub implementa Publicadores y Suscriptores de
Pubsubhubbub con las
clases Zend_Feed_Pubsubhubbub_Publisher y
Zend_Feed_Pubsubhubbub_Subscriber. Además, la implementación del
Suscriptor puede gestionar cualquier actualización de fuente reenviada desde un Hub
usando Zend_Feed_Pubsubhubbub_Subscriber_Callback. Estas clases,
sus casos de uso y sus APIs se tratan en las siguientes secciones.
En Pubsubhubbub, el Publicador es la parte que publica una fuente en vivo y la actualiza con frecuencia con contenido nuevo. Esto puede ser un blog, un agregador, o incluso un servicio web con una API pública basada en fuentes. Para que estas actualizaciones se empujen a los Suscriptores, el Publicador debe notificar a todos sus Hubs soportados de que se ha producido una actualización mediante una simple solicitud HTTP POST que contenga la URI del Tema actualizado (es decir, la fuente RSS o Atom actualizada). El Hub confirmará la recepción de la notificación, obtendrá la fuente actualizada y reenviará cualquier actualización a los Suscriptores que se hayan suscrito a ese Hub para recibir actualizaciones de la fuente correspondiente.
Por diseño, esto significa que el Publicador tiene muy poco que hacer, salvo enviar estos pings al Hub cada vez que sus fuentes cambian. Como resultado, la implementación del Publicador es extremadamente sencilla de usar y requiere muy poco trabajo de configuración y uso cuando se actualizan las fuentes.
Zend_Feed_Pubsubhubbub_Publisher implementa un Publicador
Pubsubhubbub completo. Su configuración para su uso también es sencilla, requiriendo
principalmente que se configure con el punto final URI de todos
los Hubs a los que se debe notificar sobre actualizaciones, y las
URIs de todos los Temas a incluir en las notificaciones.
El siguiente ejemplo muestra un Publicador notificando a un conjunto de Hubs sobre actualizaciones de un par de fuentes RSS y Atom locales. La clase mantiene una colección de errores que incluyen las URLs de los Hubs, de modo que la notificación pueda reintentarse más tarde y/o registrarse si alguna notificación llegara a fallar. Cada array de error resultante también incluye una clave "response" que contiene el objeto de respuesta HTTP correspondiente. En caso de errores, se recomienda encarecidamente intentar la operación de nuevo al menos una vez más en el futuro para los Puntos Finales de Hub que hayan fallado. Esto puede requerir el uso de una tarea programada para este propósito o de una cola de trabajos, aunque estos pasos adicionales son opcionales.
$publisher = new Zend_Feed_Pubsubhubbub_Publisher;
$publisher->addHubUrls(array(
'http://pubsubhubbub.appspot.com/',
'http://hubbub.example.com',
));
$publisher->addUpdatedTopicUrls(array(
'http://www.example.net/rss',
'http://www.example.net/atom',
));
$publisher->notifyAll();
if (!$publisher->isSuccess()) {
// check for errors
$errors = $publisher->getErrors();
$failedHubs = array()
foreach ($errors as $error) {
$failedHubs[] = $error['hubUrl'];
}
}
// reschedule notifications for the failed Hubs in $failedHubs
Si prefiere tener un control más concreto sobre el Publicador, los métodos
addHubUrls() y addUpdatedTopicUrls()
pasan cada valor del array a los métodos públicos singulares addHubUrl() y
addUpdatedTopicUrl(). También existen los métodos
correspondientes removeUpdatedTopicUrl() y
removeHubUrl().
También puede omitir la configuración de URIs de Hub y notificar
a cada uno por turno usando el método notifyHub(), que acepta
la URI de un punto final de Hub como único argumento.
No hay otras tareas que cubrir. La implementación del Publicador es muy sencilla, ya que la mayor parte del procesamiento y distribución de la fuente lo gestionan los Hubs seleccionados. Sin embargo, es importante detectar errores y reprogramar las notificaciones lo antes posible (con un número máximo razonable de reintentos) para garantizar que las notificaciones lleguen a todos los Suscriptores. En muchos casos, como alternativa final, los Hubs pueden sondear con frecuencia sus fuentes para ofrecer cierta tolerancia adicional a fallos, tanto en términos de su propia caída temporal como de errores o caídas del Publicador.
En Pubsubhubbub, el Suscriptor es la parte que desea recibir actualizaciones de cualquier Tema (fuente RSS o Atom). Lo consigue suscribiéndose a uno o más de los Hubs anunciados por ese Tema, generalmente como un conjunto de uno o más enlaces Atom 1.0 con un atributo rel de "hub". A partir de ese momento, el Hub enviará una fuente Atom o RSS que contenga todas las actualizaciones a la URL de Callback del Suscriptor cuando reciba una notificación de actualización del Publicador. De este modo, el Suscriptor nunca necesita visitar realmente la fuente original (aunque sigue recomendándose hacerlo en cierta medida para garantizar que las actualizaciones se recuperen si alguna vez un Hub queda fuera de línea). Todas las solicitudes de suscripción deben contener la URI del Tema al que se suscriben y una URL de Callback que el Hub usará para confirmar la suscripción y reenviar las actualizaciones.
El Suscriptor tiene, por lo tanto, dos funciones. Crear y gestionar suscripciones,
incluyendo la suscripción a nuevos Temas con un Hub, la cancelación de suscripciones (si
es necesario) y la renovación periódica de las suscripciones, ya que pueden tener una
validez limitada establecida por el Hub. Esto lo gestiona
Zend_Feed_Pubsubhubbub_Subscriber.
La segunda función es aceptar las actualizaciones enviadas por un Hub a la
URL de Callback del Suscriptor, es decir, la URI
que el Suscriptor ha asignado para gestionar las actualizaciones. La
URL de Callback también gestiona los eventos en los que el Hub
contacta con el Suscriptor para confirmar todas las suscripciones y cancelaciones de
suscripción. Esto lo gestiona el uso de una instancia de
Zend_Feed_Pubsubhubbub_Subscriber_Callback cuando se accede a la
URL de Callback.
![]() |
Importante |
|---|---|
|
Zend_Feed_Pubsubhubbub_Subscriber implementa un Suscriptor
Pubsubhubbub completo, capaz de suscribirse a, o cancelar la suscripción de,
cualquier Tema a través de cualquier Hub anunciado por ese Tema. Funciona en
conjunto con Zend_Feed_Pubsubhubbub_Subscriber_Callback, que
acepta solicitudes de un Hub para confirmar todos los intentos de suscripción o
cancelación de suscripción (para evitar el uso indebido por terceros).
Cualquier suscripción (o cancelación de suscripción) requiere la información
pertinente antes de continuar, es decir, la URI del Tema
(fuente Atom o RSS) al que se va a suscribir para recibir
actualizaciones, y la URI del punto final del Hub que
gestionará la suscripción y el reenvío de las actualizaciones. El tiempo de vida de
una suscripción puede ser determinado por el Hub, pero la mayoría de los Hubs
deberían soportar renovaciones automáticas de suscripción comprobando con el
Suscriptor. Esto lo soporta
Zend_Feed_Pubsubhubbub_Subscriber_Callback y no requiere
ningún trabajo adicional por su parte. Aun así, se recomienda encarecidamente usar el
tiempo de vida (ttl) de la suscripción proporcionado por el Hub para programar la
creación de nuevas suscripciones (el proceso es idéntico al de cualquier nueva
suscripción) para renovarla con el Hub. Aunque no debería ser estrictamente
necesario, cubre los casos en los que un Hub pueda no soportar la renovación
automática de suscripciones y descarta errores del Hub para obtener redundancia
adicional.
Con la información pertinente a mano, se puede intentar una suscripción como se muestra a continuación:
$storage = new Zend_Feed_Pubsubhubbub_Model_Subscription;
$subscriber = new Zend_Feed_Pubsubhubbub_Subscriber;
$subscriber->setStorage($storage);
$subscriber->addHubUrl('http://hubbub.example.com');
$subscriber->setTopicUrl('http://www.example.net/rss.xml');
$subscriber->setCallbackUrl('http://www.mydomain.com/hubbub/callback');
$subscriber->subscribeAll();
Para almacenar las suscripciones y ofrecer acceso a estos datos para uso general,
el componente requiere una base de datos (más adelante en esta sección se ofrece un
esquema). Por defecto, se asume que el nombre de la tabla es "subscription" y utiliza
Zend_Db_Table_Abstract internamente, lo que significa que
usará el adaptador predeterminado que haya configurado para su aplicación. También
puede pasar una instancia personalizada específica de
Zend_Db_Table_Abstract al modelo asociado
Zend_Feed_Pubsubhubbub_Model_Subscription. Este adaptador
personalizado puede ser tan simple en su intención como cambiar el nombre de la
tabla a usar, o tan complejo como usted considere necesario.
Aunque este Modelo se ofrece como una solución lista para usar por defecto, puede
crear su propio Modelo usando cualquier otra capa de backend o de base de datos
(por ejemplo, Doctrine), siempre que la clase resultante implemente la interfaz
Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface.
Un ejemplo de esquema (MySQL) para una tabla de suscripción accesible por el modelo proporcionado puede ser similar a:
CREATE TABLE IF NOT EXISTS `subscription` ( `id` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', `topic_url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `hub_url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `created_time` datetime DEFAULT NULL, `lease_seconds` bigint(20) DEFAULT NULL, `verify_token` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `secret` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `expiration_time` datetime DEFAULT NULL, `subscription_state` varchar(12) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Entre bastidores, el Suscriptor anterior enviará una solicitud al punto final del Hub que contendrá los siguientes parámetros (basado en el ejemplo anterior):
Tabla 33.9. Parámetros de la solicitud de suscripción
| Parámetro | Valor | Explicación |
|---|---|---|
| hub.callback | http://www.mydomain.com/hubbub/callback?xhub.subscription=5536df06b5dcb966edab3a4c4d56213c16a8184 |
La URI que un Hub usa para contactar con el Suscriptor y solicitar confirmación de una solicitud de (des)suscripción, o enviar actualizaciones de las fuentes suscritas. La cadena de consulta añadida contiene un parámetro personalizado (de ahí la denominación xhub). Es un parámetro de cadena de consulta preservado por el Hub y reenviado con todas las solicitudes del Suscriptor. Su propósito es permitir al Suscriptor identificar y buscar la suscripción asociada a cualquier solicitud del Hub en un medio de almacenamiento de backend. Este es un parámetro no estándar usado por este componente en lugar de codificar una clave de suscripción en la ruta de la URI, lo cual es más difícil de implementar en una aplicación de Zend Framework.
Sin embargo, dado que no todos los Hubs soportan parámetros de
cadena de consulta, seguimos recomendando encarecidamente añadir
la clave de suscripción como un componente de ruta con la forma
http://www.mydomain.com/hubbub/callback/5536df06b5dcb966edab3a4c4d56213c16a8184.
Para lograr esto, se requiere definir una ruta capaz de extraer
el valor final de la clave y luego recuperar el valor y pasarlo
al objeto Callback del Suscriptor. El valor se pasaría al método
|
| hub.lease_seconds | 2592000 |
El número de segundos durante los cuales el Suscriptor desea que una nueva suscripción permanezca válida (es decir, un TTL). Los Hubs pueden imponer su propio período máximo de suscripción. Todas las suscripciones deberían renovarse simplemente volviéndose a suscribir antes de que finalice el período de suscripción, para garantizar la continuidad de las actualizaciones. Además, los Hubs deberían intentar renovar automáticamente las suscripciones antes de que caduquen, contactando con los Suscriptores (gestionado automáticamente por la clase Callback). |
| hub.mode | subscribe |
Valor simple que indica que se trata de una solicitud de suscripción. Las solicitudes de cancelación de suscripción usarían el valor "unsubscribe". |
| hub.topic | http://www.example.net/rss.xml |
La URI del tema (es decir, la fuente Atom o RSS) al que el Suscriptor desea suscribirse para recibir actualizaciones. |
| hub.verify | sync |
Indica al Hub el modo preferido de verificación de suscripciones o cancelaciones de suscripción. Se repite dos veces en orden de preferencia. Técnicamente, este componente no distingue entre ambos modos y los trata por igual. |
| hub.verify | async |
Indica al Hub el modo preferido de verificación de suscripciones o cancelaciones de suscripción. Se repite dos veces en orden de preferencia. Técnicamente, este componente no distingue entre ambos modos y los trata por igual. |
| hub.verify_token | 3065919804abcaa7212ae89.879827871253878386 |
Un token de verificación devuelto al Suscriptor por el Hub cuando confirma una suscripción o cancelación de suscripción. Ofrece una medida de confianza de que la solicitud de confirmación se origina en el Hub correcto, para evitar el uso indebido. |
Puede modificar varios de estos parámetros para indicar una preferencia distinta.
Por ejemplo, puede establecer un valor de lease seconds diferente usando
Zend_Pubsubhubbub_Subscriber::setLeaseSeconds() o mostrar
una preferencia por el modo de verificación async usando
setPreferredVerificationMode(Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC).
Sin embargo, los Hubs conservan la capacidad de imponer sus propias preferencias, y
por esta razón el componente está deliberadamente diseñado para funcionar con casi
cualquier conjunto de opciones con una configuración mínima requerida por parte del
usuario final. ¡Las convenciones son geniales cuando funcionan!
![]() |
Nota |
|---|---|
Aunque los Hubs pueden requerir el uso de un modo de verificación específico
(ambos están soportados por |
Cancelar la suscripción de un Tema sigue exactamente el mismo patrón que el ejemplo
anterior, con la excepción de que deberíamos llamar a
unsubscribeAll() en su lugar. Los parámetros incluidos son
idénticos a los de una solicitud de suscripción, con la excepción de que
"hub.mode" se establece en "unsubscribe".
Por defecto, una nueva instancia de Zend_Pubsubhubbub_Subscriber
intentará usar un medio de almacenamiento respaldado por base de datos, que por
defecto usa el adaptador predeterminado de Zend_Db con un
nombre de tabla "subscription". Se recomienda establecer una solución de
almacenamiento personalizada cuando estos valores predeterminados no sean adecuados,
ya sea pasando un nuevo Modelo que soporte la interfaz requerida o pasando una nueva
instancia de Zend_Db_Table_Abstract al constructor del
Modelo predeterminado para cambiar el nombre de la tabla utilizada.
Cada vez que se realiza una solicitud de suscripción o cancelación de suscripción,
el Hub debe verificar la solicitud reenviando una nueva solicitud de verificación a
la URL de Callback establecida en los parámetros de
suscripción o cancelación de suscripción. Para gestionar estas solicitudes del Hub,
que incluirán todas las comunicaciones futuras que contengan actualizaciones de
Tema (fuente), la URL de Callback debería desencadenar la
ejecución de una instancia de
Zend_Pubsubhubbub_Subscriber_Callback para gestionar la
solicitud.
La clase Callback debería configurarse para usar el mismo medio de almacenamiento que la clase Subscriber. Su uso es bastante simple, ya que la mayor parte de su trabajo se realiza internamente.
$storage = new Zend_Feed_Pubsubhubbub_Model_Subscription;
$callback = new Zend_Feed_Pubsubhubbub_Subscriber_Callback;
$callback->setStorage($storage);
$callback->handle();
$callback->sendResponse();
/**
* Check if the callback resulting in the receipt of a feed update.
* Otherwise it was either a (un)sub verification request or invalid request.
* Typically we need do nothing other than add feed update handling - the rest
* is handled internally by the class.
*/
if ($callback->hasFeedUpdate()) {
$feedString = $callback->getFeedUpdate();
/**
* Process the feed update asynchronously to avoid a Hub timeout.
*/
}
![]() |
Nota |
|---|---|
Cabe señalar que
|
![]() |
Importante |
|---|---|
Es esencial que los desarrolladores reconozcan que a los Hubs solo les interesa enviar solicitudes y recibir una respuesta que verifique su recepción. Si se recibe una actualización de fuente, nunca debería procesarse en el acto, ya que esto dejaría al Hub esperando una respuesta. Más bien, cualquier procesamiento debería delegarse a otro proceso o posponerse hasta después de que se haya devuelto una respuesta al Hub. Un síntoma de no completar con prontitud las solicitudes del Hub es que este puede continuar intentando la entrega de la actualización o de la solicitud de verificación, lo que provoca que el Suscriptor procese intentos de actualización duplicados. Esto parece problemático, pero en realidad un Hub puede aplicar un tiempo de espera de solo unos segundos, y si no recibe respuesta dentro de ese tiempo, puede desconectarse (asumiendo un fallo en la entrega) y reintentarlo más tarde. Tenga en cuenta que se espera que los Hubs distribuyan grandes volúmenes de actualizaciones, por lo que sus recursos están al límite; procese las fuentes de forma asíncrona (por ejemplo, en un proceso separado, una cola de trabajos o incluso una tarea programada por cron) tanto como sea posible. |
Como se señaló anteriormente, la clase
Zend_Feed_Pubsubhubbub_Subscriber_Callback recibe la clave
combinada asociada a cualquier suscripción desde el Hub mediante uno de dos
métodos. El método técnicamente preferido es añadir esta clave a la
URL de Callback empleada por el Hub en todas las solicitudes
futuras usando un parámetro de cadena de consulta con la clave "xhub.subscription".
Sin embargo, por razones históricas, principalmente porque esto no estaba soportado
en Pubsubhubbub 0.1 (se añadió recientemente solo en la 0.2), se recomienda
encarecidamente usar el medio más compatible de añadir esta clave a la
URL de Callback, añadiéndola a la ruta de la
URL.
Así, la URL http://www.example.com/callback?xhub.subscription=key se convertiría en http://www.example.com/callback/key.
Dado que el método de cadena de consulta es el predeterminado, en previsión de un mayor nivel de soporte futuro para la especificación 0.2 completa, esto requiere algo de trabajo adicional para implementarlo.
El primer paso es hacer que la clase
Zend_Feed_Pubsubhubbub_Subscriber_Callback sea consciente de
la clave de suscripción contenida en la ruta. Por lo tanto, se inyecta manualmente,
ya que también requiere definir manualmente una ruta para este propósito. Esto se
logra simplemente llamando al método
Zend_Feed_Pubsubhubbub_Subscriber_Callback::setSubscriptionKey()
con el parámetro siendo el valor de la clave disponible desde el Router. El ejemplo
siguiente muestra esto usando un controlador de Zend Framework.
class CallbackController extends Zend_Controller_Action
{
public function indexAction()
{
$storage = new Zend_Feed_Pubsubhubbub_Model_Subscription;
$callback = new Zend_Feed_Pubsubhubbub_Subscriber_Callback;
$callback->setStorage($storage);
/**
* Inject subscription key parsing from URL path using
* a parameter from Router.
*/
$subscriptionKey = $this->_getParam('subkey');
$callback->setSubscriptionKey($subscriptionKey);
$callback->handle();
$callback->sendResponse();
/**
* Check if the callback resulting in the receipt of a feed update.
* Otherwise it was either a (un)sub verification request or invalid
* request. Typically we need do nothing other than add feed update
* handling - the rest is handled internally by the class.
*/
if ($callback->hasFeedUpdate()) {
$feedString = $callback->getFeedUpdate();
/**
* Process the feed update asynchronously to avoid a Hub timeout.
*/
}
}
}
La forma de añadir realmente la ruta que asignaría la clave añadida al final de la ruta
a un parámetro para su recuperación desde un controlador se puede lograr usando una
configuración de Route como el siguiente ejemplo con formato INI,
para su uso con el arranque de Zend_Application.
; Callback Route to enable appending a PuSH Subscription's lookup key resources.router.routes.callback.route = "callback/:subkey" resources.router.routes.callback.defaults.module = "default" resources.router.routes.callback.defaults.controller = "callback" resources.router.routes.callback.defaults.action = "index"
![[Important]](images/important.png)
![[Note]](images/note.png)