[Tutorial] notificaciones PUSH con NodeJS + WebSockets + PHP y un PLUS!!!

JALF

Lanero Reconocido
28 Feb 2007
979
Buna noche o buen día a todos, hace rato no paso por el foro y ahora vuelvo con un extenso tutorial creería yo.

¿De qué se trata todo esto?
Simple acabo de aprender como hacer notificaciones PUSH utilizando PHP + NodeJS y al final del tutorial daré un pequeño PLUS el cual ya cada quien con su imaginación podrá extender la idea :p

Primero escribamos el indice del contenido de estas entregas para poder llevar un orden:

  1. ¿Qué es NodeJS?
  2. Cómo instalar NodeJS
  3. Cómo crear un chat con NodeJS
  4. Cómo enviar un mensaje al chat desde PHP (el principio de las notificaciones PUSH)
  5. ¿Cuál es el PLUS?

Ya teniendo esto, me pongo manos a la obra con las diferentes entregas, por favor paciencia que aún estoy tembloroso de lo emocionado que estoy al saber que por fin pude aprender a hacer esto.
 
¿Qué es NodeJS?

Según la Wikipedia...

Node.js es un entorno de programación en la capa del servidor basado en el lenguaje de programación Javascript, con I/O de datos en una arquitectura orientada a eventos, y basado en el motor Javascript V8. Fue creado con el enfoque de ser útil en la creación de programas de red altamente escalables, como por ejemplo, servidores web. Fue creado por Ryan Dahl en 2009, y su evolución está apadrinada por la empresa Joyent, que además tiene contratado a Dahl en plantilla.

Node.js es similar en su propósito a Twisted de Python, Perl Object Environment para Perl, libevent para C y EventMachine para Ruby. Al contrario que la mayoría de mucho código JavaScript, no se ejecuta en un navegador, sino en el lado del servidor. Node.js implementa algunas especificaciones de CommonJS. Node.js incluye un entorno REPL para depuración interactiva.

Un ejemplo muy sencillo podría ser el siguiente:

Código:
var http = require('http');
 
http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.end('Hello World\n');
}).listen(8000);
 
console.log('Server running at http://127.0.0.1:8000/');

y esto... ¿Cómo se corre?

Sencillo guardamos el código en un archivo servidor.js por ejemplo y corremos en línea de comando así:

Código:
node servidor.js

Dicho en palabras más cortas, NodeJS es Javascript del lado del servidor y que implementa una programación orientada a eventos.
 
Cómo instalar NodeJS

En realidad yo trabajo en un Mac y todo lo instalo por el comando BREW y paquetes que ya me encuentro compilados y creo que es casi lo mismo para Windows pero voy a dar las pautas para Linux.

Como servidor de desarrollo siempre utilizo una máquina virtual con Debian6 y las instrucciones para instalar NodeJS son las siguientes:

PRIMERO
Y antes que nada todo, absolutamente TODO se hace bajo el root

SEGUNDO
Y todo lo siguiente estará bajo consola. Ponemos lo siguiente
Código:
apt-get update && apt-get install git-core curl build-essential openssl libssl-dev

TERCERO
Clonamos el repositorio de NodeJS, entramos a la carpeta clonada y hacemos chekout de la última versión estable, en mi caso lo hice con la versión 0.8.15 que es la última estable hasta el momento
Código:
git clone https://github.com/joyent/node.git
cd node
git checkout v0.8.15

CUARTO
Configuramos los el programa antes de compilarlo e instalarlo
Código:
./configure --openssl-libpath=/usr/lib/ssl --prefix=/opt/node

QUINTO
Compilamos, testeamos que haya quedado bien y por último instalamos. En la compilada se demora un poquitin así que paciencia.
Código:
make
make test
sudo make install

SEXTO
Como ya está instalado, entonces vamos a crear los enlaces simbólicos para que los comandos de node estén disponibles en cualquier parte de la consola
Código:
cd /bin/
ln -s /opt/node/bin/node
ln -s /opt/node/bin/node-waf
ln -s /opt/node/bin/npm

SEPTIMO
Ahora si todo salió bien, entonces vamos a comprobar las versiones que acabamos de instalar
Código:
node -v 
npm -v

Y si todo excelente entonces nos saldrá por cada línea de comando la versión tanto del NodeJS como la del NPM

Y listo ya tenemos nuestro NodeJS instalado y podemos hacer una prueba creamos un archivo llamado server.js y le ponemos lo siguiente:
Código:
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

Y corremos nuestro primer test
Código:
node server.js

Y si todo bien entonces entramos al navegador y escribimos la siguiente URL http://127.0.0.1:1337/

Y excelente ya tenemos nuestro NodeJS instalado y funcionando :) y para mucha más información podemos visitar la página oficial de NodeJS
 
Cómo crear un chat con NodeJS

Crear un chat con NodeJS implica varias cosas, crear un servidor y también manejar sockets y con la ayuda de HTML5 y los WebSockets ya la cosa es supremamente fácil.

Pero existen librerías muy buenas, muy estables y muy famosas en NodeJS para el manejo de Sockets y esa es la que vamos a emplear.

La librería se llama Socket.IO

Entonces los pasos para crear nuestro chat ULTRA SENCILLO, me conecto al servidor, me pongo un nick, y puedo hablar con la gente que está conectada.

OJO que no voy a pulir el código de presentación del chat, va a ser lo más sencillo posible y lo trataré de explicarlo lo mejor y más detalladamente posible y pues a la final lo que importa es el cómo se hace, el qué bonito se va a ver lo dejo en sus manos e imaginación.

1. Creamos la carpeta donde vamos a poner el servidor y sus cositas entonces digitamos el siguiente comando
Código:
mkdir chatLaneros
cd chatLaneros

2. Ahora instalamos la librería de Socket.IO que es la vamos a utilizar con NodeJS.
Código:
npm install socket.io

3. Creamos nuestro archivo server.js y le inyectamos el siguiente código
Código:
var io = require('socket.io');
var socket = io.listen(9876);
socket.on('connection', function(client) {
    client.join('laneros');

    client.on('message', function(event) {
        console.log('Mensaje recibido del cliente! ', event);
        client.send(event);
    });

    client.on('chat', function(data) {
        client.broadcast.in('laneros').emit('LALOS', data);
        client.in('laneros').emit('LALOS', data);
    });

    client.on('disconnect', function() {
        console.log('El servidor fue desconectado');
    });
});

Guardamos y ejecutamos de la siguiente manera
Código:
node server.js

Pero bueno antes de continuar voy a explicar qué está haciendo nuestro servidor sockets para el chat que estamos haciendo ;)

Código:
var io = require('socket.io');
Esto lo que hace es invocar la librería que hemos instalado e "instanciarla" en la variable "io"

Código:
var socket = io.listen(9876);
Resulta que la librería ya crea con esta línea un servidor web y lo pone a escuchar por el puerto indicado, en este caso sería por el puerto "9876" y lo instancia en la variable "socket".

Ahora si llega lo que se llama "Programación por eventos" ¿Qué sucede cuando el cliente se conecta?

Código:
socket.on('connection', function(client) {
Resulta que en esta línea estamos definiendo qué va a suceder cuando el cliente se conecte a nuestro servidor. Y lo que va a suceder es qué

Primero
El cliente se va a unir a un salón por así llamarlo, llamado "laneros"
Código:
client.join('laneros');

Segundo
Vamos a definir todos los eventos que el cliente tendrá para "llamar" al servidor una ves esté conectado. Dos eventos que siempre van a estar presentes del lado del servidor son los siguientes "message" y "disconnect".

La estructura de un evento siempre es la siguiente:
Código:
client.on([NOMBRE_EVENTO], function([VARIABLE_CON_DATOS]) {
    [CODIGO_QUE_HACER_CON_LOS_DATOS]
});

Entendiendo lo anterior ahora procedo a explicar la pequeña función central que hace funcionar a nuestro Chat.
Código:
client.on('chat', function(data) {
        client.broadcast.in('laneros').emit('LALOS', data);
        client.in('laneros').emit('LALOS', data);
    });

La función anterior... o mejor el evento anterior se llama "chat" y lo que está haciendo es, emitir a todas las personas que están metidas en el salón "laneros" la variable "data" a evento "LALOS" que se encuentra en el cliente. (Si no comprendiste no te preocupes que cuando veamos el código del cliente todas nuestras dudas se aclararán)

La segunda línea lo que hace es emitir la variable "data" al usuario que emitió el mensaje original y también hacia el evento "LALOS" que se encuentra en el cliente. Esta parte hay que hacerla ya que la primera línea emite a todos menos a la persona que lo origina inicialmente y necesitamos que la persona que lo emite también le llegue el mensaje.

Y creo que con esto estamos listos para proseguir con el código del cliente

4. Como ya vamos a pasar el cliente, primero que todo debemos de copiar la librería para el cliente quien nos va a manejar los WebSockets si es que están disponibles, sino la librería intentará manejar la situación por AJAX y en caso extremo de no poderse entonces intentará utilizar FLASH (más adelante hablaré de las diversas técnicas que existen para simular el comportamiento de sockets desde el navegador)

En la carpeta donde vamos a poner el archivo .html que es el que va de cara al usuario, vamos a copiar todo el contenido de la carpeta "dist" que se encuentra dentro de la carpeta donde hemos puesto el archivo server.js

Sí todo va según lo planeado entonces a nivel de la consola debería esos archivos encontrarse en la siguiente dirección:
Código:
node_modules/socket.io/node_modules/socket.io-client/dist/

Ahí hay 4 archivos socket.io.js, socket.io.min.js, WebSocketMainInsecure.swf y WebSocketMain.swf

Los cuales vamos a copiar en la raíz de donde vamos a poner nuestro archivo chat.html que es el que va a consumir el cliente desde el navegador.

Ahora bien... el código de nuestro chat sería el siguiente:
Código:
<!DOCTYPE html>
<html>
    <head>
        <title>Chat Laneros</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
        <script src="socket.io.min.js"></script>
        <script>
            var socket;
            $(document).ready(function() {
                socket = new io.connect('localhost', {
                    port: 9876
                });
            
                socket.on('connect', function() {
                    console.log('Client has connected to the server!');
                });

                socket.on('message', function(data) {
                    $('#chat').prepend('<div>' + data.usu + ': ' + data.msg + '</div>');
                });

                socket.on('LALOS', function(data) {
                    $('#chat').append('<div>' + data.usu + ': ' + data.msg + '</div>');
                });

                socket.on('disconnect', function() {
                    console.log('The client has disconnected!');
                    $('#chat').html('');
                });
            });

            function sendMessageToServer() {
                var message = $('#mensaje');
                var usuario = $('#nick');
                socket.emit('chat', {usu: usuario.val(), msg: message.val()});
                message.val('').focus();
            }
        </script>
    </head>
    <body>
        <div style="width: 300px">
            <div id="chat" style="height: 100%"></div>
            <div>
                <input type="text" id="nick" />
                <br />
                <input type="text" id="mensaje" /><input type="button" id="btnEnviar" value="Enviar" onclick="sendMessageToServer()" />
            </div>
        </div>
    </body>
</html>

Explico el código del cliente

Antes que nada estamos llamando la librería jQuery para un mejor manejo del HTML en forma dinámica, y luego estamos llamando la liberaría que hemos copiado de Socket.IO

Lo bueno de la librería Socket.IO es que mira por qué técnica soporta el navegador en el cual se está ejecutando técnica iFrames para navegadores realmente viejos, Long Poll (Ajax) para navegadores un poco más actuales pero que no soportan WebSockets, los mismos WebSockets para navegadores de última generación que soportan HTML5 y por si fuera poco ps si ni lo uno ni lo otro, entonces tienen un pequeño archivo .swf (archivo flash) para establecer la conexión con el servidor que hemos creado con NodeJS.

En el código del cliente lo que hacemos básicamente es

Primero: creamos una variable global llamada "socket".

Segundo: cuando el documento ya cargó y con ayuda de jQuery "$(document).ready()" entonces lo que hacemos es a la variable global instanciar la clase de Socket.IO de la siguiente manera

Código:
socket = new io.connect('localhost', {
    port: 9876
});

Resulta que hay 4 eventos que siempre deberíamos escribir porque son eventos que responden a... eventos del servidor por así decirlo y para una mejor comprensión los voy a poner en una tabla de equivalencia.
Código:
+-----------------------------------------+
+      Servidor      +      Cliente       +
+-----------------------------------------+
+ connection         + connect            +
+ message            + message            +
+ disconnect         + disconnect         +
+-----------------------------------------+

Pero si la única que se ve diferente es la de conexión en cliente y en servidor y las otras? entonces vamos explicar qué sucede para tener claro el movimiento de esto:

connection/connect: resulta que cuando alguien se conecta al servidor, se activa el evento connection y cuando el cliente se ha conectado al servidor entonces se activa el evento connect.

message/message: resulta que cuando en el servidor escribimos lo siguiente client.send(mensaje); entonces quien recibe ese mensaje en el cliente es el evento message y cuando desde el cliente escribimos socket.send(mensaje); entonces quien recibe ese mensaje en el servidor es el evento message ¿ahora si se comprende por qué ese evento es mejor ponerlo? porque ese tipo de mensajes es más bien utilizado como para decirle algo al servidor o accionar algo especial porque de resto cuando necesitamos accionar un evento desde el servidor hacía el cliente entonces útilizamos la siguiente manera client.emit([NombreEvento], dataJSON); y cuando necesitamos accionar un evento desde el cliente al servidor, entonces lo escribimos de la siguiente manera socket.emit([NombreEvento], datosJSON);

Para resumir, el metodo para accionar un evento es emit.

disconnect/disconnect: resulta y acontece que cuando un cliente se desconecta, en el servidor se dispara inmediatamente el evento disconnect y cuando el servidor por X o Y motivo se cae, se cierra o pasa algo que saca a todo el mundo, entonces en el cliente se activa también el evento disconnect.

Y ya para finalizar explico la siguiente función Javascript que está en el cliente
Código:
function sendMessageToServer() {
    var message = $('#mensaje');
    var usuario = $('#nick');
    socket.emit('chat', {usu: usuario.val(), msg: message.val()});
    message.val('').focus();
}

Lo que se hace aquí es muy sencillo y resulta que estoy sacando el mensaje y el usuario en variables y luego estoy armando un JSON con dichos valores de la siguiente manera
Código:
{usu: usuario.val(), msg: message.val()}
Para después accionar el evento chat desde el cliente en el servidor de la siguiente manera y pasando el JSON
Código:
socket.emit('chat', {usu: usuario.val(), msg: message.val()});

En el servidor quien me recibe lo anterior es el siguiente código
Código:
client.on('chat', function(data) {
    client.broadcast.in('laneros').emit('LALOS', data);
    client.in('laneros').emit('LALOS', data);
});

El cual ya creo que he explicado pero vuelvo a explicar para que no retrocedan ;)
Primero hay que decirle a toda la gente que está conectada que ha llegado un nuevo dato ¿cómo hago esto?
Código:
client.broadcast.emit('LALOS', data);
Aquí entra el metodo broadcast, pero resulta que solo se lo quiero decir a las personas que están en el salón "laneros" entonces para eso mejor empleamos la siguiente línea:
Código:
client.broadcast.in('laneros').emit('LALOS', data);

Ahora bien resulta que el metodo broadcast solo a todo el muno MENOS a la persona quien ha originado el mensaje; entonces para poder también avisarle a la persona que originalmente ha generado dicho mensaje y que se encuentra en el canal "laneros", procedemos con la siguiente línea:
Código:
client.in('laneros').emit('LALOS', data);

Y listo chicos hasta aquí el chat funcionando de lo lindo, ya podemos empezar a crear chats más vistosos.

Para más información (y si que la hay) los invito a ver la documentación de Socket.IO aquí
 
Node.js se me hace interesante pero lo encuentro un poco enredado, bueno lo que estamos intentando usar aqui es todo lo que se usa en Facebook, node + websocket +PHP

Mas tarde lo pruebo gracias y suscribiendo!

Desde mi WP 7.8
 
Node.js se me hace interesante pero lo encuentro un poco enredado, bueno lo que estamos intentando usar aqui es todo lo que se usa en Facebook, node + websocket +PHP

Mas tarde lo pruebo gracias y suscribiendo!

Desde mi WP 7.8

Hola compañero, si es verdad es algo enredado en un principio porque no estamos acostumbrados a trabajar por eventos.

Pero te aseguro que después de trabajar unos cuantos ejemplos la cosa cambia y se vuelve ultra fácil, además en internet se encuentra muchos pero muchos ejemplos.

Salu2

PD: en la noche que llegue a casa completaré el post del tutorial :)
 
Listo el tutorial en un 60%

Ahora solo me resta escribir la parte de PHP (notificaciones PUSH) y el PLUS y listo mi aporte..... tranquilos que ya mismo estoy escribiendo el de PHP.

Salu2
 
Cómo enviar un mensaje al chat desde PHP (el principio de las notificaciones PUSH)

Algo de historia... resulta que una vez uno tiene corriendo el servidor de sockets con NodeJS entonces enviarle datos en teoría, es relativamente fácil, pero la pregunta es ¿cómo lo puedo hacer?

sé que a através de la línea de comandos por medio del comando CURL sería muy fácil... pero resulta que la cosa no era tan color de rosa como se pensaba.

Haciendo algo de ingeniería inversa a la conexión creada por por el navegador, me dí cuenta que:

PRIMERO
EL navegador le decía al servicio que se iba a conectar y le pasaba una variable vía GET, una variable llamada "t" y tenía el tiempo en formato microtime (si la memória no me falla)

SEGUNDO
El servidor te responde con una "KEY" la cual utilizarás para crear una conexión socket utilizando dicha "KEY"

TERCERO
listo listo ya tenemos todo claro y de echo lo hice en PHP con las funciones de CURL y todo corre de lo lindo, el servidor me acepta, me manda la KEY y.... ahora como le digo al servidor.... vea pa' active tal evento con tales variables?...

Y he ahí donde empecé a patinar un poco y nada y nada y nada.... hasta que me encontré unas librerías para PHP que ya lo resolvían... yo la verdad que ni corto ni perezoso las probé y después de ver que funcionaban y cómo funcionaban por dentro... me dije... CARAJO!!!! ESTABA FACIL pero... voy a utilizar mejor las librerías; pues estas lo hacen todo y es muy fácil de usarlas :p

¿Defecto? solo se pueden usar en PHP5.3

Y su funcionamiento para el ejemplo que tenemos sería el siguiente
Código:
<?php

require('ElephantIO/Client.php');
use ElephantIO\Client as ElephantIOClient;

$elephant = new ElephantIOClient('http://localhost:9876');

$elephant->init();
$elephant->send(
    ElephantIOClient::TYPE_EVENT,
    null,
    null,
    json_encode(array('name' => 'chat', 'args' => array('usu' => 'PHP', 'msg' => 'Hola desde PHP')))
);
$elephant->close();

?>

Como podrán notar la forma de usarse es realmente FACIL, pues incluyo la librería base, hago uso del namespace y luego procedo a instanciar la clase pasando la dirección de donde se encuentra nuestro servidor de sockets corriendo junto con el puerto.

Arranco creo la conexión con el metodo init() y luego utilizo el metodo send() para enviarle un JSON con las variables name equivalente al evento a accionar y args equivalente a las variables que le voy a pasar.

En resumen amigos aquí tenemos las notificaciones PUSH ya de aquí para adelanta lo que resta es mucha imaginación y ganas de construir cositas :)

La página de la librería elephant.io está aquí junto a toda su documentación ;)

PD: cualquier duda pregunta y atentos que esta semana público el PLUS que le puse a todo esto ;)
 
En teoría, leyendo el código fuente de una página que use un chat en Node.js, se podría hacer cualquier tipo de intrusión para enviar código malicioso o para hacer uso de un server de Node para una página externa (otro dominio para el cuál no fue diseñando):

Incluso, con la solución para hacer notificaciones PUSH, se abren más las posibilidades para "hackear" un servidor Node utilizando PHP.

Colaboro con algo que encontré en stackoverflow para impedir que los usuarios envíen código HTML malicioso en los mensajes :

PHP:
    function textVal(str) {
        var strippedText = $("<div/>").html(str).text();
        return strippedText;
    }

    function sendMessageToServer() {
        var message = $('#mensaje');
        var usuario = $('#nick');
        socket.emit('chat', {usu: usuario.val(), msg: textVal(message.val())});
        message.val('').focus();
    }
 
Hola Yon gracias por tu aporte

Es verdad este fue uno de los primero temas que abrodé "la inyección de codigo malicioso" y la verdad cuando escribes en NodeJS puro sule ser algo "peligroso" pero en realidad no pude hacer algun tipo de inyección pero igual encontré temas que hablan de eso y lo más recomendado es usar Frameworks como por ejemplo Express quien tiene metodos para escapar a inyecciones de código malicioso en NodeJS.

De todas formas gracias por tu aporte y me pondré más juicioso a ver como es la cosa y estaré por aqui reportando :)

Mil gracias ;)
 
Jalf la conexión que haces con el archivo de PHP, le introduces un require
require('ElephantIO/Client.php');, ¿qué archivo es en concreto? En la documentación oficial no lo encutro. Gracias de antemano, a por cierto ¿Sabes si se puede configurar el webSocket para hacer envíos exclusivos a un determinado usuario conectado? De nuevo gracias por tu aportación
 
Jalf la conexión que haces con el archivo de PHP, le introduces un require
require('ElephantIO/Client.php');, ¿qué archivo es en concreto? En la documentación oficial no lo encutro. Gracias de antemano, a por cierto ¿Sabes si se puede configurar el webSocket para hacer envíos exclusivos a un determinado usuario conectado? De nuevo gracias por tu aportación

Amigo lanero, el archivo lo saqué mirando el código en github

https://github.com/wisembly/elephant.io

Pero veo que el proyecto lleva bastante sin actualizar, habría que probar que funcione con las últimas versiones.

Por otro lado mi propuesta de envío de información a un usuario concreto... sería crearle un salón exclusivo a ese usuario.

De todas maneras voy a retomar el proyecto con las últimas versiones y traeré una actualización teniendo en cuenta lo que me sugieres.

Enviado desde mi HUAWEI MT7-L09 mediante Tapatalk
 
Hola buen día!!
disculpa, he seguido las instrucciones, pero antes de empezar con el codigo del cliente la carpeta dist no existe o_O
 

Los últimos temas