tall eye

the higher the point of view, the more you can see

Posts tagged xmpp

0 notes &

Brincando com APIs de XMPP

Este artigo foi publicado em 03/02/2010 no finado blog.talleye.com, estou apenas mudando ele pra cá.

Primeiramente, antes de ler este artigo, se quiser saber o básico sobre XMPP, leia meu artigo anterior: Breve introdução ao XMPP.

Hoje em dia, todo indivíduo envolvido com Web já consumiu alguma API HTTP, isso já é parte do dia a dia de qualquer desenvolvimento de aplicação web. Porém, nos últimos meses, começaram a aparecer alguns websites oferecendo APIs em outros protocolos, entre eles, o XMPP. Consumir uma API com esse protocolo é um pouco mais complicado que uma versão HTTP, mas o escopo e os benefícios obtidos são bem diferentes, como demonstraremos neste artigo.

XMPP em aplicações Web

Enquanto não temos suporte completo aos websockets do HTML5, que seria uma maneira de criar conexões TCP a partir do browser, a alternativa é usar a extensão BOSH do XMPP, que especifica como um cliente deve usar o XMPP via uma conexão HTTP. Vamos olhar com mais detalhe nos componentes envolvidos nesse tipo de conexão no diagrama abaixo, roubado diretamente da especificação da extensão:

    Servidor XMPP
        |
        |  [unwrapped data streams]
        |
    BOSH Connection Manager
        |
        |  [HTTP + <body/> wrapper]
        |
    Cliente (Browser)
  • Entre o browser e o BOSH Connection Manager: por meio de long polling ou de qualquer outra técnica Comet, o browser envia requisições HTTP que respeitam a especificação BOSH (o HTTP + <body> wrapper no diagrama acima) para um gerenciador de conexões (BOSH connection manager), cuja responsabilidade é identificar a sessão XMPP e manter as informações dessa sessão.
  • Entre o BOSH connection manager e o servidor XMPP: o connection manager deve criar e manter uma conexão TCP para cada sessão aberta com o servidor XMPP e é dessa forma que a tradução HTTP para TCP é feita.

Esse BOSH connection manager pode ser tanto um componente da sua infra quanto implementado dentro do próprio servidor XMPP. Você pode encontrar mais informações sobre esses conectores em http://xmpp.org/tech/bosh.shtml.

Strophejs

O Strophejs é uma biblioteca que implementa um cliente de XMPP em javascript, logo, pode ser utilizado em aplicações web. Entre as funcionalidades que essa biblioteca implementa, podemos citar:

  • está em conformidade com a especificacão BOSH e o XMPP;
  • é cross-browser;
  • possui builders e usa jQuery, o que facilita o manuseio de XML;
  • suporta vários métodos de autenticação;
  • faz long polling e permite requisições HTTP cross-domain por meio de um componente flash;

Mashup de comparação em tempo real

O objetivo dessa aplicação é permitir que o usuário possa comparar dois assuntos (palavras-chave) de sua escolha em tempo real. Por exemplo, podemos disparar uma busca para comparar as linguagens Java e Ruby em tempo real.

Para que isto seja possível, iremos utilizar a API XMPP do Collecta, um site de busca em tempo real. A API do Collecta utiliza o modelo Publish-Subscribe para publicar os resultados de busca em tempo real para os usuários da API. Então vamos rever o cenário:

  • Logo ao abrir o site a aplicação web connecta anonimamente no servidor XMPP por meio do Strophe;
  • O usuário entra com uma busca;
  • A aplicação web se inscreve em um nó pubsub (no caso, a busca);
  • Ao confirmar a inscricão, a aplicação web está pronta para receber os eventos publicados;
  • Ao receber um evento, a aplicação web faz um parse do XML e apresenta o resultado em HTML;
  • O usuário pode agora comparar os resultados;

Todo o código deste exemplo está no Github caso queira acompanhar e executar o exemplo. Antes de detalharmos o que acontece em cada fluxo, as dependências dessa aplicação são as seguintes:

  • jQuery: auxilia bastante no parsing de XML;
  • flXHR.js: componente flash que permite requisições cross-domain, necessário para a aplicação se conectar num servidor XMPP em outro domínio;
  • strophejs: sem ele não conseguimos usar o XMPP;
  • dependências do strophejs: códigos para gerar md5, base64 e usar o componente flash;
  • API key do Collecta: para usar a API do Collecta você precisa de uma chave.

Mesmo sendo todo implementado em HTML e Javascript, é importante servir essa página em um servidor web, para que o componente flash funcione.

Então vamos apresentar como se implementa cada fase do fluxo dessa aplicação (o foco será apenas no código javascript):

Ao abrir o site

A inicialização da conexão ao servidor XMPP já é feita logo que o usuário abre o site.

// config.js
var Config = {
    API_KEY: 'YOUR_COLLECTA_API_KEY',
    BOSH_SERVICE: 'http://collecta.com/xmpp-httpbind',
    HOST: "guest.collecta.com"
};

No arquivo de configuração acima, temos 3 itens importantes: a chave da API, o BOSH connection manager ao qual iremos conectar (tente abrir essa URL no seu browser) e o host (servidor XMPP) que queremos conectar. Temos um problema nesse ponto, a chave da API vai ser exposta aos usuários. Para o caso da API do Collecta, o único problema seria outro usuário consumir o rate limit, mas mesmo assim o time da Collecta deve procurar alternativas ainda, uma vez que a API é nova.

// real-time-comparison.js
// initiating a BOSH connection to create an anonymous connection to the Collecta XMPP server
connection = new Strophe.Connection(Config.BOSH_SERVICE);
connection.connect(Config.HOST, null, onConnect);

Graças ao strophejs, basta criar uma conexão como é feito acima, passando o serviço BOSH que deseja usar e depois chamar a função connect desse objeto. Os parâmetros passados são o host desejado, o usuário (que é null porque desejamos conectar anonimamente) e a referência à função que deve ser chamada (callback) ao receber algum status do processo de conexão.

// real-time-comparison.js
function onConnect(status)
{
    if (status == Strophe.Status.CONNECTING) {
    // ... outros status
    } else if (status == Strophe.Status.CONNECTED) {
        // adding one handler for each type of XMPP stanza
        connection.addHandler(onPresence, null, 'presence', null, null, null);
        connection.addHandler(onIq, null, 'iq', null, null, null);
        connection.addHandler(onMessage, null, 'message', null, null, null);
        console.log("Sending presence...");
        // app is available to use only after sending and receiving presence from the server
        connection.send($pres().tree());
    }
}

A função onConnect, ao receber o status informando que está conectado, irá criar os 3 handlers necessários para tratar o recebimento de cada um dos 3 stanzas existentes no XMPP. O primeiro parâmetro é o callback de referência e depois o tipo do stanza (leia a documentação do strophejs para saber quais são os outros parâmetros). Após a criação dos handlers, em toda conexão XMPP que é feita, o cliente deve enviar um stanza de presença para o servidor. Como estamos conectando anonimamente, neste caso, após o envio desse stanza, o servidor deve retornar um stanza de presença informando o Jabber ID do usuário anônimo.

// real-time-comparison.js
function onPresence(prs) {
    // in this case, presence got from the XMPP server means to activate UI and allow user to enter the 2 terms to compare
    console.log("Got presence!");
    anonymous_jid = $(prs).attr('to');
    // ... outras coisas
    return true;
}

Sendo assim, quando o servidor retorna a presença, nós simplesmente parseamos o xml com jQuery e pegamos o atributo “to”, que indica pra quem o servidor está mandando essa presença, ou seja, o usuário da aplicação. É importante retornar true no fim desse callback, pois sem ele o strophejs irá apagar esse handler. Com o return true, o handler não será apagado e deverá ser reutilizado no próximo stanza de presença que esse cliente receber.

O usuário entra com uma busca

Neste momento o usuário pode entrar com a busca no campo, ao clicar no botão “Go”, a seguinte função é chamada:

// real-time-comparison.js
function startComparison() {
    // creates 2 search subscriptions and send the request to Collecta XMPP API via Strophejs
    console.log("Subscribing to nodes: "+ terms[0] +" and "+ terms[1]);
    connection.send(Collecta.subscribeSearchStanza(terms[0]).tree());
    connection.send(Collecta.subscribeSearchStanza(terms[1]).tree());
}

Inscrição em um nó Pubsub de busca

Para evitar repetição de código, criamos uma pequena biblioteca chamada Collecta que monta os stanzas necessários para inscrever e desinscrever em um nó Pubsub, que representam as buscas. Nesta biblioteca também se encontra uma função para converter os resultados de busca de XML para JSON a fim de ser usado na hora de construir o HTML para o usuário.

// real-time-comparison.js
var Collecta = {
    subscribeSearchStanza: function(searchName) {
    // generate XML for a search subscription on Collecta XMPP API
    return $iq({type: 'set', from: anonymous_jid, to: 'search.collecta.com', id: searchName })
      .c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
        .c('subscribe', {node: 'search', jid: anonymous_jid})
      .up().c('options')
        .c('x', {xmlns: 'jabber:x:data', type: 'submit'})
          .c('field', {"var": 'FORM_TYPE', type: 'hidden'})
            .c('value').t('http://jabber.org/protocol/pubsub#subscribe_options')
          .up().up().c('field', {"var": 'x-collecta#apikey'})
            .c('value').t(Config.API_KEY)
          .up().up().c('field', {"var": 'x-collecta#query'})
            .c('value').t(searchName);
 },
 // ...

O trecho de código acima apresenta como devemos criar um stanza utilizando o XML Builder implementado no strophejs. No caso dessa função, estamos criando um stanza do tipo iq para realizar a inscrição do nosso usuário (observador) em um nó PubSub. A construção desse stanza respeita a especificação PubSub do XMPP. Dê uma olhada na documentação da API do Collecta para ver como deve ser esse stanza que é enviado ao servidor.

Confirmação da inscrição

// real-time-comparison.js
function onIq(xml) {
    // in this case, the IQ stanzas handled are just subscription results
    xmlDom = $(xml);
    var iqId = xmlDom.attr('id');
    if (iqId == terms[0] || iqId == terms[1]) {
      if (xmlDom.find('subscription:first').attr('subscription') == 'subscribed') {
        console.log("Subscribed to "+iqId);
      }
    }
    return true;
}

Após enviar o stanza de inscrição, devemos esperar uma resposta do servidor. Quem processa essa resposta é o handler que tínhamos criado anteriormente. O procedimento é simples, com jQuery podemos navegar no DOM da resposta, se o id da resposta for o mesmo da busca realizada e o status da inscrição é “subscribed”, então o usuário está inscrito e agora só precisa aguardar os itens publicados.

Recebendo um evento publicado

// real-time-comparison.js
function onMessage(msg) {
    // messages stanzas are converted to JSON and then prepended to the correspondent panel in the UI
    var result = Collecta.processItem($(msg));
    for (var i=0; i < result.length; i++) {
       $('#term'+ result[i].term +"panel")
         .prepend("<p>["+result[i].category+"] - <a href=\""+result[i].url+"\" target=\"_blank\" title=\" Access "+terms[result[i].term]+" information\">"+result[i].title+"</a><br />..."+result[i].description+"...<br />Published in "+result[i].published+"</p>");
       };
    return true;
}

Os eventos publicados vêm em um stanza “message” e extendidos com a especificação PubSub. O único passo necessário e realizar o parse dessa resposta e convertê-lo para JSON, assim poderemos construir todo o HTML necessário para repassar esses resultados de busca para o usuário de forma mais fácil.

Após todo esse processo, o usuário já pode visualizar os resultados na página assim que eles forem sendo publicados no nó PubSub. Ufa!

Quando há a necessidade de se obter resultados em tempo real na web, o antigo modelo de puxar a informação (pull) por meio de uma requisição HTTP já não é tão eficiente, pois perde-se tanto no desempenho do servidor, que terá uma alta carga devido às inúmeras requisições desnecessárias feitas, quanto na velocidade da informação, pois entre uma requisição e outra pode-se perder o efeito tempo real desejado.

Está crescendo cada vez mais o push como modelo para aplicações Web, na qual o servidor deve fornecer a informação para os clientes web logo que ela for publicada. Juntamente com o modelo de push, o modelo Publish-Subscribe também está se provando efetivo, uma vez que não é só implementado em XMPP, mas também em Webhooks, Pubsubhubbub, entre outros.

O diferencial do XMPP neste caso é a especificação da extensão Publish-Subscribe, que é bastante detalhada e cobre vários fluxos que podem existir no modelo PubSub, além é claro dos vários servidores que já implementam este módulo. Vale a pena experimentar.

Filed under api bosh collecta javascript pubsub xmpp

0 notes &

Breve introdução ao XMPP

Este artigo foi publicado em 02/02/2010 no finado blog.talleye.com, estou apenas mudando ele pra cá.

Apesar de ser um protocolo não muito novo (sim, 10 anos não é muito novo), o número de aplicações que utilizam XMPP (Extensible Messaging and Presence Protocol) vêm crescendo substancialmente apenas nos últimos anos. Exemplos de aplicações que usam XMPP para implementar a troca de mensagens entre componentes e usuários incluem: Google Wave, Chesspark, Collecta, Superfeedr, entre outros.

Ele muitas vezes é lembrado apenas como um protocolo para criar chats e restrito apenas à plataforma cliente, mas não nos prendamos a esse pensamento simplista, existe muito mais poder que pode ser extraído das mensagens trafegadas por essa camada de aplicação.

Segundo os autores do protocolo, o XMPP é uma tecnologia aberta para comunicação em tempo real que permite a criação de vários tipos de aplicações, tais como instanting messaging, presença, chats multi-usuários, chamadas de voz e vídeo, colaboração, middleware, content syndication e qualquer transmissão de dados via XML. Talvez a definição mais curta possível seja: é um protocolo para a criação de middlewares orientado a mensagens. E canais de troca de mensagens existem em qualquer sistema distribuído, e acredite, XMPP pode ser usado em cada um deles, desde que, é claro, isso traga benefícios ao sistema ou ao desenvovimento.

Do que é feito o XMPP?

O protocolo XMPP situa-se na camada de aplicação e é comumente transportado por conexões TCP. Ele constitui-se de XML e possui apenas 3 unidades básicas de transporte, chamados stanzas: presence, message e iq. Iremos apresentar brevemente cada stanza, mas se quiser mais detalhes acesse o RFC do XMPP:

Presence

<presence from="cipriani@talleye.com/casa">
    <status>Ouvindo música...</status>
</presence>

O stanza de presença tem a funcionalidade de notificar servidores e a lista de contatos sobre o status de um usuário na rede XMPP. Essa é uma das grandes vantagens do XMPP em relação à outros protocolos de mensageria, pois a propagação de presença faz parte da especificação e portanto deve ser implementado por clientes e servidores por padrão. No exemplo acima, o usuário cipriani@talleye.com/casa está notificando a rede XMPP de que ele está online, e mais, ouvindo música.

Outro detalhe importante é a forma de endereçamento dos stanzas, ou melhor, de que forma você identifica unicamente um usuário na rede XMPP. No exemplo, o usuário é o cipriani@talleye.com/casa. “cipriani” é o nome do usuário, “talleye.com” é o host ao qual esse usuário pertence e a palavra “casa” é o recurso, utiliza-se o recurso para permitir que um mesmo usuário esteja conectado ao mesmo tempo em vários clientes diferentes (o usuário poderia ter um recurso de nome “escritório” por exemplo). Esses três pedaços constituem o Jabberd ID, ou JID, e é utilizado em todos os tipos de stanzas.

Message

<message to="pedro@shorteye.com/escitorio"
     from="cipriani@talleye.com/casa"
     type="chat" >
    <body>Cadê você?</body>
</message>

No stanza acima, um usuário envia uma mensagem para outro, do tipo “chat” com o conteúdo (body) “Cadê você”. Os stanza de mensagem é enviado e o cliente não deve esperar nenhuma resposta do servidor informando que a entrega foi feita, ele apenas envia para o usuário destino ou armazena caso o usuário esteja offline.

IQ (Info Query)

<iq type="set" id="an_id"
     from="cipriani@talleye.com/casa"
     to="talleye.com">
    <query xmlns="jabber:iq:roster"/>
</iq>

Diferente do stanza message, cada stanza iq enviado ao servidor deve obter uma resposta, por isso existe o atributo type, que pode ter valores set ou get, e como resposta o usuário recebe um outro iq de tipo result ou error, dependendo do status.

No caso do stanza acima, estamos requisitando ao servidor XMPP a lista de contatos de um usuário. E a tag query dentro do iq nos remete a uma outra funcionalidade do protocolo XMPP, talvez a mais importante, que são as extensões!

Extensões

O significado da sigla XML é Extensible Markup Language. O XMPP usa XML para implementar o protocolo. Opa, peraí, quer dizer então que eu posso extender o XMPP? Sim!

O desenvolvedor é livre para incluir qualquer XML dentro ou fora dos stanzas, desde que seu cliente e servidor XMPP conheçam o seu significado. Para que o protocolo não virasse bagunça e todos pudessem usufruir de servidores e bibliotecas que implementam extensões, contruiu-se uma comunidade para especificar e publicar as especificações.

Após 10 anos, há um número alto de exensões, por exemplo, algumas disponíveis são:

  • Service Discovery: extensão para que um usuário possa acessar quais serviços um servidor XMPP oferece.
  • Multi-User Chat: extensão para criar salas de bate-papo multi usuários.
  • Jingle: extensão que permite chamadas via vídeo.
  • Bidirectional-streams Over Synchronous HTTP (BOSH): essa extensão permite um cliente HTTP conectar em um servidor XMPP por meio de um connection manager, cuja responsabilidade é converter uma sessão XMPP de HTTP para TCP.
  • Publish-Subscribe (PubSub): essa extensão implementa o modelo Publicador/Observador, no qual um observador pode assinar um canal do publicador. E cada vez que um item for publicado nesse canal, o observador irá recebê-lo.
  • Tem muito mais de onde essas vieram, sugiro dar uma olhada.

Por quê XMPP?

Quais as razões que faria com que eu utilizasse o XMPP como protocolo ao invés de qualquer outra implementação, como AMQP ou até um protocolo mais simples escrito por mim mesmo?

Como tudo na computação, não tem nenhuma tecnologia que é boa para todos os casos, por isso a melhor resposta é aquela de sempre: depende! :-) Primeiro vamos dar uma olhada em alguns motivos que favorecem o uso do XMPP:

Por ter uma comunidade forte o XMPP é um protocolo sólido, vários fluxos já foram pensados, testados e é um protocolo ativo, que se adapta às necessidades dos seus usuários. Além disso ele é apoiado por bibliotecas e servidores que implementam e oferecem as principais extensões, é descentralizado (e por isso, escalável), seguro e extensível.

Por outro lado, dependendo da complexidade da aplicação, o XMPP pode ser muito burocrático e um protocolo mais simples é bem mais efetivo. O mesmo acontece se você tentar utilizar uma extensão XMPP quando na verdade já existe algum outro protocolo especialista em um modelo de mensagem.

Por experiência própria, já iniciei um sistema que nem imaginava utilizar XMPP, mas à medida que a arquitetura foi crescendo ele foi se tornando um protocolo interessante e por fim se transformou no principal componente. Enfim, o importante é conhecer suas opções, estudar e experimentar. Mas sempre tenha na sua lista o XMPP e suas extensões, pois muito do que o arquiteto de software precisa criar já está escrito em algumas de suas especificações.

Filed under mensageria middleware protocol xmpp