Post on 26-Sep-2018
transcript
Servicio de atención al lector: usershop@redusers.com
Como sabemos, las redes sociales han tomado un
protagonismo muy fuerte en el mundo de la comunicación,
debido a la experiencia que generan en los usuarios de
eventos en tiempo real y escalabilidad.
Desarrollaremos un proyecto utilizando Node como lenguaje
de programación y Redis como modelo de persistencia.
Desarrollode una red social
▼ El porqué del proyecto ..............2
▼ Definición del proyecto..............2
Creación del proyecto ........................ 3
Confi guración del archivo app.js ......... 4
Registro y login de usuarios ............. 12
Creación de la página principal ........ 25
Defi nición de los eventos
para un usuario logueado ................. 27
Envío de una solicitud de amistad .... 31
Lista de amigos conectados ............. 40
Informar cuando
se conecta un usuario ....................... 43
Creación del sistema de chat............ 48
Creación del sistema de posts .......... 53
Vista de la base de datos.................. 59
▼ Resumen ...................................61
▼ Actividades ...............................62
APÉNDICE. DESARROLLO DE UNA RED SOCIAL2
www.redusers.com
El porqué del proyectoLa razón por la cual desarrollaremos una red social es porque reúne
todas las características y condiciones que deben tener los sistemas
escalables estudiados a lo largo de este libro.
Una red social tiene que ser capaz de manejar eventos de interacción
con los usuarios en tiempo real y debe funcionar bajo un modelo de
persistencia que permita operar con millones de registros y solicitudes
en el menor tiempo posible. A nuestro ejemplo lo llamaremos
SocialRedis y utilizará las siguientes tecnologías:
• *Node.js
• *Socket.IO
• *Express
• *Redis
• *Crypto (módulo para encriptación)
• *Ejs (motor de plantillas)
• *Session.socket.io (módulo para manejo de sesiones en Socket.IO)
El sistema se encargará de funcionar de manera autónoma como
servidor propio, y ofrecerá todas las características específi cas (tales
como ruteo, manejo de plantillas y estilos css, eventos de tiempo
real e interacción con la base de datos).
Defi nición del proyectoConsiste en el desarrollo de una red social similar a Facebook, donde
los usuarios podrán registrarse; desde el primer momento se mostrará
en tiempo real la cantidad de usuarios registrados. Consideremos
que podrán enviar solicitudes de amistad y contar con un sistema de
notifi caciones, que permitirá aceptar o rechazar una solicitud en el
mismo instante en que otro usuario la envía. Además, se podrán crear
posts para compartir entre los amigos. Por último, implementaremos
un sistema de chat donde los usuarios podrán tener múltiples
conversaciones en forma simultánea.
SISTEMAS WEB ESCALABLES 3
www.redusers.com
Figura 1. En la pantalla principal los usuarios pueden compartirinformación e interactuar en tiempo real con los demás usuarios.
Instalación de herramientas previasPara comenzar, necesitamos una herramienta que nos permita
administrar la base de datos. Como vamos a trabajar exclusivamente
con Redis y Node, podemos utilizar el módulo Redis Commander
instalándolo mediante el siguiente comando:
npm install -g redis-commander
También es recomendable utilizar Nodemon,
ya que nos evitaremos reiniciar la aplicación en
cada cambio que hagamos. Lo podemos instalar
mediante el siguiente comando:
npm install -g nodemon
Creación del proyectoPara dar inicio a nuestro ejemplo debemos abrir una consola
y dirigirnos a la unidad C:; una vez ahí, ejecutaremos los siguientes
comandos para crear la aplicación e instalar los módulos necesarios:
GRACIAS A NODEMON
NO SERÁ NECESARIO
REINICIAR LA
APLICACIÓN LUEGO
DE CADA CAMBIO
APÉNDICE. DESARROLLO DE UNA RED SOCIAL4
www.redusers.com
express –e SocialRedis
cd SocialRedis && npm install
npm install redis
npm install crypto
npm install socket.io
npm install session.socket.io
Utilizamos el parámetro –e para crear la aplicación, con lo cual
indicamos que vamos a utilizar el módulo ejs como motor de plantillas;
luego instalamos los módulos redis, crypto, socket.io y sesión.socket.io.
Una vez creado el entorno de trabajo, podemos ejecutar la aplicación
con Nodemon y comprobar que está en funcionamiento. En la consola
escribimos el siguiente comando y posteriormente abrimos un
navegador para verifi car su funcionamiento:
nodemon app.js
Por defecto, el sistema va a funcionar en la siguiente dirección:
http://127.0.0.1:3000.
Confi guración del archivo app.jsAhora trabajaremos directamente en la aplicación. Lo primero que
haremos es confi gurar el archivo principal, dirigiéndonos a la raíz
del proyecto y abriendo el archivo app.js. En este punto tenemos que
realizar algunos cambios importantes, por lo que vamos a borrar todo
el contenido del archivo e iremos agregando el código necesario. Para
comenzar, vamos a incluir los módulos:
Express provee una extensa lista de ejemplos que cubren todos los aspectos del framework, como auten-
ticación, manejo de plantillas, cookies, solución de errores, parámetros de entrada y administración de
sesiones, entre otros. Podemos acceder a los ejemplos en la siguiente dirección: https://github.com/
visionmedia/express/tree/master/examples.
APLICACIONES DE EJEMPLO DE EXPRESS
SISTEMAS WEB ESCALABLES 5
www.redusers.com
// defi nicion de modulos
var express = require(‘express’)
, routes = require(‘./routes’)
, user = require(‘./routes/user’)
, http = require(‘http’)
, path = require(‘path’)
, redis = require(‘redis’)
, crypto = require(‘crypto’)
, ssio = require(‘session.socket.io’);
Inicializamos las variables agregando lo siguiente:
// inicializacion de variables
var app = express()
, server = http.createServer(app)
, io = require(‘socket.io’).listen(server)
, sessionStore = new express.session.MemoryStore()
, cookieParser = express.cookieParser(‘!@#$%^&*()1234567890qwerty’)
, sessionIO = new ssio(io, sessionStore, cookieParser);
Para continuar defi nimos el puerto que utilizará el sistema, el
directorio y también el motor de plantillas:
// confi guraciones para todos los entornos
app.set(‘port’, process.env.PORT || 3000);
app.set(‘views’, __dirname + ‘/views’);
app.set(‘view engine’, ‘ejs’);
Actualmente los desarrolladores nos encontramos con una gran cantidad de frameworks MVC para organizar y
estructurar las aplicaciones en JS. Para ayudarnos a resolver el problema existe un proyecto llamado TodoMVC,
el cual ofrece una funcionalidad similar para varios frameworks como Backbone, Ember o AngularJS.
SELECCIONAR UN FRAMEWORK
APÉNDICE. DESARROLLO DE UNA RED SOCIAL6
www.redusers.com
Para continuar nos encargamos de establecer las herramientas
o middlewares de Express que utilizaremos:
// middlewares de Express
app.use(express.favicon());
app.use(express.logger(‘dev’));
app.use(express.bodyParser());
app.use(cookieParser);
app.use(express.session({ store: sessionStore }));
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, ‘public’)));
Posteriormente realizamos la defi nición del uso del manejador de
errores para el entorno de desarrollo:
// confi guraciones para el entorno de desarrollo
if (‘development’ == app.get(‘env’)) {
app.use(express.errorHandler());
}
Para continuar, determinamos las variables globales que serán
visibles desde todos los archivos del sistema:
// defi nicion de variables globales
global.usrEnLinea = [];
global.titulo = ‘Social Redis’;
global.autor = ‘Carlos Alberto Benitez [2013]’;
global.db = redis.createClient(6379, ‘localhost’);
global.io = io;
global.sessionIO = sessionIO;
global.crypto = crypto;
La variable usrEnLinea servirá para almacenar los usuarios conectados
al sistema; titulo y autor son parte de la leyenda del sitio; db es un objeto
SISTEMAS WEB ESCALABLES 7
www.redusers.com
que contendrá la conexión a la base de datos; io
es también un objeto y contendrá la instancia de
Socket.io; en sessionIO almacenamos la instancia del
objeto sesión.socket.io, que nos servirá para acceder
a las variables de sesión desde una conexión por
socket; por último, la variable crypto nos servirá
para encriptar las claves de acceso.
A continuación debemos realizar la defi nición
de las rutas que manejaremos en el sistema.
Primero ruteamos las solicitudes por GET para el
acceso y salida del sistema:
// GET - defi nicion de las rutas
app.get(‘/’, routes.index);
//app.get(‘/salir’, user.logout);
Notemos que, para el ruteo de la página principal, utilizamos el
método index del archivo index.js y, para la salida, el método logout del
archivo user.js. Consideremos que esta última está comentada ya que
aún no realizamos la defi nición del método.
Luego, a través del método POST, determinamos el ruteo para las
acciones de registro, login, creación de un post, solicitud de
amistad y respuesta a una solicitud de amistad.
// POST - defi nicion de las rutas
//app.post(‘/registro’, user.registro);
//app.post(‘/login’, user.login);
//app.post(‘/setPost’, user.setPost);
//app.post(‘/setSolicitud’, user.setSolicitud);
//app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud);
Todas las acciones defi nidas utilizarán los métodos del archivo
user.js, pero como todavía no las hemos creado están comentadas.
A medida que vayamos creando los métodos iremos descomentando
el ruteo para cada una de ellas. Por último, ponemos en marcha el
servidor indicándole el puerto que vamos a utilizar:
LA VARIABLE CRYPTO
PERMITIRÁ LA
ENCRIPTACIÓN DE
LAS CLAVES
DE ACCESO
APÉNDICE. DESARROLLO DE UNA RED SOCIAL8
www.redusers.com
// listen
server.listen(app.get(‘port’), function(){
console.log(‘Express server listening on port ‘ + app.get(‘port’));
});
Defi nición de la apariencia del sistemaVamos a establecer la apariencia del sistema. Primero debemos
dirigirnos al directorio public/ y renombrar las carpetas images por
img, javascripts por js y stylesheets por css; haremos estos cambios
únicamente para simplifi car la legibilidad de los archivos.
Dentro de la carpeta css/, vamos a crear un archivo con el nombre
style.css, con el siguiente código:
body{background: url(“/img/bg.jpg”) repeat scroll 0 0 transparent; font:
12px/1.5em Arial,Helvetica,sans-serif; margin: 0; padding: 0; text-align: center;
color: #444444;}
header{height: 50px; padding: 20px 5px;}
header #buscador{width: 370px; fl oat: left;}
header #buscador #tBuscar{width: 270px;}
header #buscador #bBuscar{height: 28px;}
header #notifi cacion{fl oat: right; padding: 9px 0 0; width: 203px;}
header #notifi cacion #usr{fl oat: left; font-size: 14px; height: 100%; margin:
5px 0; width: 150px;}
h1{background: url(“/img/icon-profi le.png”) no-repeat scroll left top transparent;
color: #666; fl oat: left; font-size: 29px; line-height: 55px; margin: 0; padding: 0
10px 0 42px; text-align: left; text-shadow: 1px 1px 0 #FFF; width: 175px;}
input[type=”text”],
SISTEMAS WEB ESCALABLES 9
www.redusers.com
input[type=”password”]{margin: 7px 0 0;}
a{color: #444444; display: block; text-decoration: none;}
nav{clear: both; fl oat: left; width: 140px; padding: 0 10px;}
nav ul{list-style: none outside none; padding: 0; margin: 0;}
nav ul li{border: 1px solid transparent; cursor: pointer; font-size: 15px; height:
100%; line-height: 46px; padding: 0; width: auto;}
nav ul li:hover,nav ul .active {background-color: #EEEEEE; background-
image: -moz-linear-gradient(center top , #EEEEEE, #E0E0E0); border: 1px solid
#CCCCCC; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset; color: #333333;}
input[type=”submit”]{background: url(“/img/bg-verde.png”) repeat-x left top
#47C516; border: 1px solid #149E1F; border-radius: 2px 2px 2px 2px; color:
#FFFFFF; padding: 5px; text-align: center;}
footer{clear: both; height: 80px; padding: 10px; text-align: center;}
footer p{margin: 0 auto; padding: 30px 0; width: 300px;}
.contenedor{background: url(“/img/bg-sombras.png”) no-repeat scroll center top
transparent; margin: 0 auto; min-height: 600px; width: 850px;}
.marco{border: 1px solid #CCCCCC; border-radius: 5px; -moz-border-radius:5px;
-webkit-border-radius: 5px; padding: 7px;}
.marco.blanco{background-color: #FFFFFF;}
.marco.gris{background-color: #F8F8F8;}
.marco.celeste{background-color: #BAD9F1;}
.login{fl oat: right; padding: 5px 15px; text-align: right; width: 401px; font-size:
15px;}
APÉNDICE. DESARROLLO DE UNA RED SOCIAL10
www.redusers.com
.tecnologias {fl oat: left; height: 400px; width: 400px;}
.tecnologias img{margin: 20px 0;}
.registro{min-height: 215px; padding: 5px 15px;}
.registro input{display: block; width: 395px;}
.error {background: url(“/img/icono_error.gif”) no-repeat 10px 50% #FAEBE7;
border: 1px solid #F16048; color: #DF280A; padding: 3px 3px 3px 30px; text-
align: justify;}
.columna_derecha{fl oat: right; margin: 0 5px; width: 431px;}
.usuarios_registrados{font-size: 15px; margin: 15px 0; padding: 5px 15px;
width: 399px;}
.usuario{text-align: right; min-width: 140px; fl oat: left; padding: 11px 10px 0;
font-size: 14px;}
.contenido{width: 438px; fl oat: left;}
.usuarios{fl oat: left; margin: 0 5px; width: 180px; text-align: left;}
.usuarios span{font-size: 13px; font-weight: bold;}
#notifi cacion ul, .usuarios ul{margin: 0; min-height: 40px; list-style: none; pad-
ding: 0;}
#notifi cacion ul li, .usuarios ul li{border-bottom: 1px solid #CCCCCC; margin:
10px 0; cursor: pointer; padding: 5px;}
#notifi cacion ul li:hover, .usuarios ul li:hover{background-color: #47C516;}
.chat{clear: both; height: 181px; bottom: 0; margin: 0 5px; padding: 0 3px;
width: 828px; text-align: left; position: absolute; z-index: 1000;}
SISTEMAS WEB ESCALABLES 11
www.redusers.com
.ventana{margin: 0 2px; fl oat: left; width: 186px;}
.ventana input[type=”text”]{margin: 0 2px; width: 179px;}
.usuariosLista{min-height: 400px;}
.avatar{fl oat: left; width: 30px; height: 30px;}
.avatar img{width: 30px; height: 30px;}
.bloque{text-align: left; margin-bottom: 5px; fl oat: left; width: 421px;}
.bloque .header{line-height: 45px; height: 30px; border-bottom: 1px solid #CCC;
text-align: left; margin-bottom: 10px;}
.bloque .header img{margin-right: 10px; fl oat: left; width: 30px; height: 30px;}
.bloque .header .bloque_usuario{font-weight: bold; margin: 0 5px 0 0;}
.bloque .header .bloque_fecha{fl oat: right;}
.contador{background: url(“/img/bg-azul.png”) repeat-x left top #53AFEC;
border: 1px solid #315EA8; border-radius: 2px 2px 2px 2px; color: #FFFFFF;
padding: 5px; text-align: center; display: block; fl oat: left; font-size: 18px; font-
weight: bold; height: 17px; width: 30px;}
#postFrm{width: 436px;}
#postFrm textarea {display: block; height: 63px; width: 422px;}
#postFrm input[type=”submit”]{margin-right: 0; fl oat: right;}
#solicitudes{height: 200px; width: 200px; background-color: #F8F8F8; posi-
tion: absolute; z-index: 100; display: none; top: 64px;}
.bSalir{display: inline-block; position: relative; cursor: pointer; text-shadow: 1px
1px 0 #FFF; top: -57px; width: 81px; right: -106px;}
APÉNDICE. DESARROLLO DE UNA RED SOCIAL12
www.redusers.com
Dado que solo hemos defi nido los aspectos visuales, no se va a
explicar el código en detalle.
Registro y login de usuariosAhora vamos a trabajar sobre la primera pantalla. Para esto,
primero crearemos las plantillas que se reutilizarán en la aplicación.
Generaremos un archivo nuevo llamado header.ejs, que guardaremos
en el directorio views/ con el siguiente contenido:
<!DOCTYPE html>
<html lang=”es”>
<head>
<meta charset=”utf-8” />
<title><%= titulo %></title>
<link rel=”stylesheet” href=”/css/style.css” />
<link rel=”shortcut icon” href=”/img/favicon.png” >
</head>
<body>
<div class=”contenedor”>
Simplemente hemos creado una estructura HTML básica que luego
incluiremos en las otras plantillas. El elemento <title> contendrá la
variable titulo, que hemos defi nido anteriormente en el archivo app.js.
Vamos a crear un nuevo archivo llamado footer.ejs con código que sigue:
<footer>
<p>
<b><%= titulo %></b> - <%= autor %>
</p>
</footer>
</div>
<script type=”text/javascript” src=”/socket.io/socket.io.js”></script>
<script type=”text/javascript” src=”http://ajax.googleapis.com/ajax/libs/jque-
ry/1.9.0/jquery.min.js”></script>
<script type=”text/javascript” src=”/js/script.js”></script>
SISTEMAS WEB ESCALABLES 13
www.redusers.com
<!--[if lt IE 9]><script src=”http://html5shiv.googlecode.com/svn/trunk/html5.
js”></script><![endif]-->
</body>
</html>
Al igual que header.ejs, este archivo contendrá las variables globales
titulo y autor defi nidas en el archivo app.js; además, se incluyen los
archivos JavaScript que se necesitarán del lado del cliente. El archivo
html5.js permite utilizar elementos HTML5 en versiones de Internet
Explorer menores a 9.
Ya estamos en condiciones de crear la pantalla de acceso.
Generaremos un archivo dentro del directorio views/ con el nombre
index.ejs y el siguiente contenido:
<!-- inluimos el archivo header.ejs -->
<% include header %>
<header>
<h1><%= titulo %></h1>
<!-- formulario de login -->
<div class=”login marco blanco”>
<form id=”loginFrm” action=””>
<input type=”text” name=”usuario” value=”” placeholder=”Usuario”/>
<input type=”password” name=”clave” value=”” placeholder=”Clave”/>
<input type=”submit” id=”loginBtn” value=”Entrar” />
</form>
</div>
Como hemos visto, Express ofrece un entorno en el cual podemos, entre otras cosas, manejar el ruteo,
y con Node es posible crear nuestro propio servidor web. Una interesante propuesta es desarrollar el
mismo sistema utilizando PHP y Apache para identifi car las ventajas y desventajas de cada uno y defi nir
cuándo utilizar una arquitectura u otra.
REPLICAR EL EJEMPLO UTILIZANDO PHP
APÉNDICE. DESARROLLO DE UNA RED SOCIAL14
www.redusers.com
</header>
<!-- logos de Redis y Node -->
<section class=”tecnologias”>
<img src=”img/bg_redis.png” alt=”Redis”/>
<img src=”img/bg_nodejs.png” alt=”Node.js”/>
</section>
<!-- formulario de registro -->
<section class=”columna_derecha”>
<article class=”registro marco blanco”>
<h2>Usuario Nuevo:</h2>
<div>
<form id=”nuevoFrm” action=””>
<input type=”text” name=”nombre” value=”” placeholder=”Nombre” />
<input type=”text” name=”apellido” value=”” placeholder=”Apellido” />
<input type=”text” name=”usuario” value=”” placeholder=”Usuario” />
<input type=”password” name=”clave” value=”” placeholder=”Clave” />
<input type=”text” name=”correo” value=”” placeholder=”Correo” />
<input type=”submit” id=”nuevoBtn” value=”Registrar” />
</form>
</div>
</article>
<!-- elemento para mostrar los usuarios registrados -->
<aside class=”usuarios_registrados marco blanco”>
<p>Ya se registraron <span id=”totalUsuarios”>0</span> usuarios</p>
</aside>
</section>
<!-- inluimos el archivo footer.ejs -->
<% include footer %>
En este código, primero incluimos la plantilla header y defi nimos el
título mediante la variable global. Luego, creamos un archivo llamado
home.ejs y lo guardamos en el mismo directorio que index.ejs, por el
momento sin contenido. También generamos el formulario de login
y, más abajo, agregamos los logos de Redis y Node para lograr una
apariencia más atractiva. Creamos el formulario de registro de usuarios
y, debajo, un elemento que permite mostrar la cantidad de usuarios
registrados en el sistema. Por último, incluimos la plantilla footer.
SISTEMAS WEB ESCALABLES 15
www.redusers.com
Una vez defi nida la plantilla de inicio, necesitamos crear la función
que se encargará de renderizar el contenido cuando se accede al
sistema. Para esto, abrimos el archivo index.js dentro del directorio
routes/ y reemplazamos todo el contenido por lo siguiente:
exports.index = function(req, res){
// inicializamos la variable se sesion de usuario
req.session.usr = req.session.usr || ‘’;
// defi nimos la plantilla a renderizar
plantilla = req.session.usr === ‘’ ? ‘index’ : ‘home’;
// renderizamos la palntilla
res.render(plantilla,
{ titulo: titulo,
autor: autor,
usuario: req.session.usr
}
);
};
En este código exportamos la función index para que sea accesible
desde cualquier archivo y, dentro de ella, verifi camos si la variable req.
session.ur tiene algún contenido: como aún no la hemos defi nido, está
vacía. Por lo tanto, renderizaremos la plantilla index.ejs pasándole las
variables globales titulo y autor; la variable usuario, en esta instancia,
está vacía. Si ejecutamos la aplicación y accedemos a la dirección
http://127.0.0.1:3000 deberemos ver lo que muestra la Figura 2.
Siempre es buena idea formar parte de los grupos de usuarios de las tecnologías que nos interesan.
Express posee una lista que actualmente cuenta con alrededor de dos mil usuarios, discutiendo cerca de
cinco mil temas. El grupo utiliza la plataforma Google Groups y podemos unirnos a través del siguiente
enlace: https://groups.google.com/forum/#!forum/express-js.
GRUPO DE USUARIOS DE EXPRESS
APÉNDICE. DESARROLLO DE UNA RED SOCIAL16
www.redusers.com
Figura 2. Al ingresar al sistema, los usuarios podránregistrarse o iniciar sesión para poder interactuar con amigos.
A continuación defi niremos el archivo que manejará las acciones
del usuario, creando un archivo llamado script.js, que guardaremos
en el directorio js/ con el siguiente contenido:
$(document).ready(function(){
// registro de usuario
$(document).on(‘submit’, ‘#nuevoFrm’, function(e){
e.preventDefault();
setUsuario($(this));
});
});
// funcion para mostrar el mensaje de error en los formularios
function mostrarMensajeFormulario(form, mensaje){
if (form.fi nd(‘p’).size())
form.fi nd(‘p’).show().html(mensaje);
else
SISTEMAS WEB ESCALABLES 17
www.redusers.com
form.prepend(‘<p class=”error”>’ + mensaje + ‘</p>’);
form.fi nd(‘p’).fadeOut(7000);
}
// metodo POST de registro
function setUsuario(form) {
$.post(“/registro”, form.serialize(),
function(respuesta){
if (respuesta.codigo === 201)
window.location = respuesta.mensaje;
else
mostrarMensajeFormulario(form, respuesta.mensaje);
}
);
}
En el código defi nimos la acción para el formulario de registro,
donde ejecutamos la función setUsuario()
para enviar los datos al servidor. Si la
respuesta del servidor es 201, redirigimos al
usuario a la pantalla principal del sistema;
en el caso contrario, ejecutamos la función
mostrarMensajeFormulario(), que creará un elemento
<p> para mostrar el mensaje recibido.
En la función setUsuario() hacemos un POST
por Ajax a la ruta /registro (en el archivo app.js
tenemos comentado el ruteo para esta solicitud).
Ahora simplemente tenemos que descomentarla,
pasando de esto:
//app.post(‘/registro’, user.registro);
a esto:
app.post(‘/registro’, user.registro);
UNA RESPUESTA 201
REDIRIGE AL USUARIO
A LA PANTALLA
PRINCIPAL
DEL SISTEMA
APÉNDICE. DESARROLLO DE UNA RED SOCIAL18
www.redusers.com
Debemos, ahora, defi nir el método registro, para lo cual abrimos
el archivo user.js (ubicado en el directorio routes/), borramos todo el
contenido y escribimos lo siguiente:
// registramos el usuario
exports.registro = function(req, res) {
var nombre = req.param(‘nombre’),
apellido = req.param(‘apellido’),
usuario = req.param(‘usuario’),
clave = req.param(‘clave’),
correo = req.param(‘correo’);
if (nombre.length < 1 || apellido.length < 1 || usuario.length < 1 || clave.length <
1 || correo.length < 1 ){
res.send({codigo: 204, mensaje: ‘Complete todos los campos’ });
return;
}else{
// guardar en DB
db.get(‘usuario:’ + usuario + ‘:uid’, function (err, reply) {
if (reply === null){
// obtenemos el proximo uid
db.incr(‘global:ultimoUid’, function (err, uid) {
// seteamos el uid al usuario
db.set(‘usuario:’ + usuario + ‘:uid’, uid);
// encripto las claves
var hashClave = crypto.createHash(‘sha256’).update(clave).digest(‘hex’);
// seteamos los campos al usuario
db.hmset(‘uid:’ + uid, {‘nombre’ : nombre,
‘apellido’ : apellido,
‘usuario’ : usuario,
‘clave’ : hashClave,
‘correo’ : correo
}
);
// incremento la cantidad de usuarios (ver si dejar o no)
db.sadd(‘usuarios’,uid);
SISTEMAS WEB ESCALABLES 19
www.redusers.com
// seteamos las variables de session
req.session.usr = {‘uid’ : uid,
‘nombre’ : nombre,
‘apellido’ : apellido,
‘usuario’ : usuario
};
// emitimos el total de usuarios
getTotalUsuarios();
res.send({codigo: 201, mensaje: ‘/’ });
});
}else
res.send({codigo: 204, mensaje: ‘Error: El usuario ya existe’ });
});
}
};
En este código obtenemos los parámetros
enviados del formulario y verifi camos que ya
no esté registrado el usuario; incrementamos la
clave global:ultimoUid y creamos un usuario con
este valor. Encriptamos la clave y guardamos los
atributos en la base de datos, agregamos el uid a
usuarios y creamos la variable de sesión con uid,
nombre, apellido y usuario. Hacemos una llamada
a la función getTotalUsuarios(), y retornamos el
código 201 con la ruta para la redirección.
A continuación, debemos defi nir la función getTotalUsuarios() que
hemos utilizado anteriormente:
// emitimos el total de usuarios
var getTotalUsuarios = function () {
db.scard(‘usuarios’, function (err, cant) {
io.sockets.emit(“setTotalUsuarios”, cant);
});
}
LUEGO DE ENCRIPTAR
LA CLAVE DEBEMOS
GUARDAR LOS
ATRIBUTOS DE LA
BASE DE DATOS
APÉNDICE. DESARROLLO DE UNA RED SOCIAL20
www.redusers.com
Utilizaremos la función getTotalUsuarios() para obtener la cantidad de
usuarios registrados y emitir el resultado a través del evento llamado
setTotalUsuarios, utilizando Socket.IO. Al fi nal del archivo exportamos la
función para que pueda ser invocada desde cualquier archivo del sistema:
// exportamos las funciones
exports.getTotalUsuarios = getTotalUsuarios;
En síntesis, cuando se registra un usuario, emitimos un evento para
informar a todos los miembros la cantidad de usuarios registrados. El
paso siguiente es retornar al archivo script.js para capturar el evento
setTotalUsuarios. Primero nos conectamos al socket, incluyendo la
siguiente línea al principio del archivo:
// nos conectamos al socket
var sockets = io.connect(‘http://127.0.0.1:3000’);
Luego, dentro de la función $(document).ready(), capturamos el evento:
// io - cuando se registra un usuario muestro el total
sockets.on(‘setTotalUsuarios’, mostrarTotalUsuarios);
En este código hemos indicado que, cuando se reciba el evento, se
ejecutará la función mostrarTotalUsuarios(), que defi nimos de la siguiente
manera:
// cuando recibe el total de usuarios lo muestra en el contenedor
function mostrarTotalUsuarios(data){
$(‘#totalUsuarios’).html(data);
}
En este código obtenemos la cantidad de usuarios y los mostramos
en el elemento. Podemos probar cómo se ve el proyecto abriendo un
navegador y registrando un usuario. Si todo salió bien, el usuario
SISTEMAS WEB ESCALABLES 21
www.redusers.com
nuevo será redirigido, por ahora, a una página en blanco o, en caso
contrario, se mostrará un error.
Figura 3. Para que un usuario pueda registrarse debe completartodos los campos; de otra manera, se mostrará un mensaje de error.
A continuación vamos a crear el login de usuario. En el archivo script.
js, dentro de la función $(document).ready(), agregamos lo siguiente:
// login
$(document).on(‘submit’, ‘#loginFrm’, function(e){
e.preventDefault();
login($(this));
});
Redis provee una vía para testear, agregar y mejorar características de la base de datos: solo es necesa-
rio clonar el repositorio ofi cial de Github. Otra alternativa para contribuir con Redis es reportar bugs o re-
solver los existentes. Ambas opciones son defi nidas en detalle en el enlace http://redis.io/community.
CONTRIBUIR CON REDIS
APÉNDICE. DESARROLLO DE UNA RED SOCIAL22
www.redusers.com
En el mismo archivo agregamos la función login():
// metodo POST de login
function login(form) {
$.post(“/login”, form.serialize(),
function(respuesta){
if (respuesta.codigo === 201)
window.location = respuesta.mensaje;
else
mostrarMensajeFormulario(form, respuesta.mensaje);
}
);
}
En el código anterior hicimos un POST por Ajax a la ruta /login,
en el cual enviamos los datos del formulario al igual que el registro.
En el caso de que la respuesta del servidor sea 201, redireccionamos
al usuario a la ruta indicada; en el caso contrario, mostramos el
mensaje mediante la función mostrarMensajeFormulario(). Ahora
descomentamos el ruteo para /login en el archivo principal (es decir,
en app.js), pasando de esto:
//app.post(‘/login’, user.login);
a esto:
app.post(‘/login’, user.login);
Necesitamos crear la función login(). Escribiremos lo siguiente en el
archivo user.js:
// verifi camos el usuario y creamos la variable de sesion
exports.login = function(req, res){
var usuario = req.param(‘usuario’),
clave = req.param(‘clave’);
SISTEMAS WEB ESCALABLES 23
www.redusers.com
if (usuario.length < 1 || clave.length < 1){
res.send({codigo: 204, mensaje: ‘Complete todos los campos’ });
return;
}else{
db.get(‘usuario:’ + usuario + ‘:uid’, function (err, uid) {
if (uid === null)
res.send({codigo: 204, mensaje: ‘Error: El Usuario no existe’ });
else{
// obtenemos todos los atributos del usuario
db.hgetall(‘uid:’ + uid, function (err, usuario) {
// encriptamos la clave
var hashClave = crypto.createHash(‘sha256’).update(clave).digest(‘hex’);
if (hashClave == usuario.clave) {
// creamos la variable se sesion
req.session.usr = {‘uid’ : uid,
‘nombre’ : usuario.nombre,
‘apellido’ : usuario.apellido,
‘usuario’ : usuario.usuario
};
res.send({codigo: 201, mensaje: ‘/’ });
}else
res.send({codigo: 204, mensaje: ‘Error: Usuario o clave incorrectos’ });
});
}
});
}
}
En este código, primero verifi camos que los parámetros sean
diferentes a vacío y luego comprobamos que el usuario exista: en caso
de existir obtenemos los atributos del usuario y comparamos la clave.
Si la clave almacenada en la base de datos coincide con la ingresada,
creamos la variable de sesión con los datos del usuario, devolvemos el
código 201 y la ruta para redireccionar. En el caso contrario, retornamos
el código 204 y generamos un mensaje de error.
APÉNDICE. DESARROLLO DE UNA RED SOCIAL24
www.redusers.com
Figura 4. Cuando un usuario intente accedersin completar los campos, se mostrará una advertencia.
Es importante considerar que cada vez que se intente acceder con
un nombre de usuario que no se encuentre registrado en el sistema, se
procederá a mostrar un mensaje de carácter informativo a los usuarios
que están intentando conectarse.
Figura 5. En el caso de que un usuario no existaen la base de datos, simplemente mostramos otro mensaje.
SISTEMAS WEB ESCALABLES 25
www.redusers.com
Creación de la página principalUna vez que el usuario se ha registrado o ha iniciado sesión, es
redireccionado a la página principal con la variable de sesión creada.
Por lo tanto, renderizamos la plantilla home.ejs. A continuación
abriremos este archivo y agregaremos el siguiente código:
<!-- inluimos el archivo header.ejs -->
<% include header %>
<header>
<h1><%= titulo %></h1>
<!-- formulario de busqeda -->
<div id=”buscador”>
<input type=”text” id=”tBuscar” placeholder=”Buscar...” class=”marco” />
<input type=”submit” id=”bBuscar” value=”Buscar” />
</div>
<!-- elemento para mostrar las notifi caciones -->
<div id=”notifi cacion”>
<span class=”usuario” ><%= usuario.nombre %> <%= usuario.apellido
%></span>
<span class=”contador”>
<span id=”valorContador”>0</span>
</span>
<!-- elemento para mostrar la lista de solicitudes -->
<ul id=”solicitudes” class=”marco blanco”></ul>
</div>
<a href=”/salir” class=”bSalir”>Salir</a>
</header>
<!-- columna para la lista de amigos -->
<aside class=”usuarios marco blanco”>
<span>Podes chatear con:</span>
<div id=”usuariosAmigos” class=”usuariosLista”>
<p>No hay amigos conectados...</p>
APÉNDICE. DESARROLLO DE UNA RED SOCIAL26
www.redusers.com
</div>
</aside>
<!-- formulario para crear y mostrar los post -->
<section class=”contenido”>
<article class=”bloque”>
<form id=”postFrm” action=””>
<textarea name=”post” placeholder=”Contale al mundo...”
class=”marco”></textarea>
<input type=”submit” id=”postBtn” value=”Postear” />
</form>
</article>
<div id=”posts”></div>
</section>
<!-- columna para usuarios nuevos -->
<aside class=”usuarios marco blanco”>
<span>Podes ser amigo de:</span>
<div id=”usuariosNuevos” class=”usuariosLista”>
<p>No hay usuarios nuevos...</p>
</div>
</aside>
<!-- contenedor de las ventanas de chat -->
<aside class=”chat”></aside>
<!-- inluimos el archivo footer.ejs -->
<% include footer %>
En este código hemos incluido el archivo header.ejs y defi nimos el título
con el contenido de la variable global y los elementos para el buscador.
Luego determinamos el área para las notifi caciones y la lista de
solicitudes, y defi nimos el enlace para salir del sistema y el contenedor
donde el usuario verá la lista de amigos conectados. Creamos un
contenedor para el formulario y el listado de posts. Defi nimos el
contenedor para los usuarios que no son amigos del usuario actual.
Generamos el contenedor de las ventanas de chat e incluimos footer.ejs.
Una vez creada la plantilla podemos acceder para ver su estructura.
SISTEMAS WEB ESCALABLES 27
www.redusers.com
Figura 6. Cuando el usuario inicie sesión, verá la listade amigos, el área central para los posts y la columna con usuarios nuevos.
Defi nición de los eventospara un usuario logueado
A continuación vamos a crear los eventos que emitirá y recibirá el
usuario cuando inicia sesión a través de Socket.IO. Para esto, abrimos
el archivo index.js y agregamos lo siguiente en la primera línea para
tener acceso a las funciones defi nidas en el archivo user.js:
var user = require(‘./user.js’);
Luego, dentro de la función index(), vamos a utilizar el objeto
sessionIO para acceder a la variable de sesión y desencadenar los
eventos. Para esto escribimos lo siguiente:
sessionIO.on(‘connection’, function (err, socket, session) {
//usuarios logueados
if(session.usr != ‘’){
// guardamos el socket.id del usuario actual
APÉNDICE. DESARROLLO DE UNA RED SOCIAL28
www.redusers.com
usrEnLinea[session.usr.uid] = socket.id;
// obtenemos los usuarios que no son amigos
user.getUsuariosNuevos(session.usr.uid);
}else{
// obtenemos la cantidad de usuarios
user.getTotalUsuarios();
}
});
En este código, capturamos la conexión y verifi camos si la variable
session.usr contiene información. En el caso de que un usuario se
haya logueado, almacenamos el identifi cador del socket en el array
global usrEnLinea con su uid como identifi cador, lo que nos permitirá
identifi car el socket mediante el cual está conectado cada usuario para
poder enviarle mensajes privados. Luego, vamos a invocar a la función
getUsuariosNuevos() del archivo user.js, al cual le pasaremos como
parámetro el uid del usuario actual. Por lo tanto, debemos crear esta
función en el archivo user.js:
// obtenemos los usuarios que no son amigos
var getUsuariosNuevos = function(uid){
db.sdiff(‘usuarios’, ‘uid:’ + uid + ‘:amigos’, function (err, usuarios) {
if (usuarios) {
var usrNuevos = [];
usuarios.forEach(function(id){
// verifi camos que no sea el usuario actual
if (uid != id){
// verifi camos que no tenga una solicitud pendiente
db.sismember(‘uid:’ + id + ‘:solicitudes’, uid, function (err, solicitudEnviada)
{
if(solicitudEnviada == 0){
// obtenemos la informacion del usuario
db.hgetall(‘uid:’ + id, function (err, usuario) {
usrNuevos.push({‘uid’: id,
SISTEMAS WEB ESCALABLES 29
www.redusers.com
‘nombre’ : usuario.nombre,
‘apellido’ : usuario.apellido
});
// emitimos los usuarios
if (usrNuevos.length == usuarios.length - 1)
io.sockets.socket(usrEnLinea[uid]).emit(‘setUsuariosNuevos’, usr-
Nuevos);
});
}
});
}
});
}
});
}
En este código utilizamos el comando sdiff de Redis para obtener los
usuarios que no son amigos del usuario actual; luego, con cada uno
verifi camos que no sea el uid del usuario conectado, y comprobamos
que no tenga una solicitud de amistad pendiente. Obtenemos los datos
y los almacenamos en un array. Cuando fi naliza la lista, emitimos el
evento setUsuariosNuevos al usuario actual a través del array usrEnLinea
con el índice identifi cado por el uid pasado como parámetro de la
función. Para que la función esté disponible desde cualquier lugar,
agregamos lo siguiente al fi nal del archivo:
exports.getUsuariosNuevos = getUsuariosNuevos;
Ahora necesitamos capturar el evento del lado del cliente. Para esto,
agregamos lo siguiente al método $(document).ready() del archivo script.js:
//io - mostramos los usuarios que no son amigos
sockets.on(‘setUsuariosNuevos’, mostrarUsuariosNuevos);
En el código, recibimos el evento y ejecutamos la función
mostrarUsuariosNuevos(), que defi niremos de la siguiente manera:
APÉNDICE. DESARROLLO DE UNA RED SOCIAL30
www.redusers.com
// muestra los usuarios nuevos
function mostrarUsuariosNuevos(data){
if (data.length) {
$(‘#usuariosNuevos’).html(‘<ul>’);
data.forEach(function (usuario) {
var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido ;
$(‘#usuariosNuevos ul’).append(‘<li uid=”’ + usuario.uid +’”>’ + nombreUsu-
ario + ‘</li>’);
});
$(‘#usuariosNuevos’).append(‘</ul>’);
};
}
En el código que acabamos de presentar agregamos al elemento
#usuariosNuevos, de esta manera podremos acceder a la lista recibida
de los usuarios que aún no son amigos del que se encuentra conectado
actualmente. Podemos efectuar la prueba de esta funcionalidad
registrando varios usuarios.
Figura 7. Cuando el usuario iniciesesión obtendrá una lista con los usuarios que no son amigos,
a quienes les podrá enviar una solicitud.
SISTEMAS WEB ESCALABLES 31
www.redusers.com
Envío de una solicitud de amistadVamos a desarrollar la funcionalidad que nos permitará que el
usuario envíe una solicitud de amistad.
Figura 8. Para enviar una solicitud de amistad solo seránecesario hacer clic en algún nombre de la lista de usuarios.
Necesitamos capturar el evento click de la lista. Para esto, agregamos
lo siguiente en el método $(document).ready() del archivo script.js:
// se envia una solicitud de amistad
$(document).on(‘click’, ‘#usuariosNuevos ul li’, function(){
setSolicitud($(this).attr(‘uid’));
});
Por defecto, Express usa el motor de plantillas Jade, el cual es muy bueno pero abstrae de una manera
signifi cante la utilización de elementos HTML. Una alternativa es utilizar EJS, más amigable y simple al
crear estructuras complejas. Para instalarlo cuando creamos una aplicación, debemos escribir el siguien-
te comando: express -e nombreDeLaApp.
INSTALAR EXPRESS CON EJS
APÉNDICE. DESARROLLO DE UNA RED SOCIAL32
www.redusers.com
En este código hemos capturado el evento click de la lista de
usuarios y ejecutamos la función setSolicitud(), que defi nimos en
el mismo archivo de la siguiente manera:
// se envia una solicitud de amistad
function setSolicitud(uid) {
$.post(“/setSolicitud”, ‘uid=’ + uid,
function(respuesta){
if (respuesta.codigo === 201)
$(‘#usuariosNuevos ul li[uid=”’ + uid + ‘”]’).fadeOut(1000);
else
$(‘#usuariosNuevos ul li[uid=”’ + uid + ‘”]’).html(respuesta.mensaje);
}
);
}
Aquí hicimos un POST por Ajax a la ruta /
setSolicitud, enviando el uid del usuario a que se
le mandará la solicitud. En el caso de tener como
respuesta el código 201, quitamos al usuario de la
lista, y en el caso contrario, mostramos el mensaje
en el mismo lugar.
Para continuar es necesario que
descomentemos el ruteo para la petición /
setSolicitud en el archivo principal, para lo cual
abriremos y cambiaremos el archivo app.js.
Pasaremos de esto:
//app.post(‘/setSolicitud’, user.setSolicitud);
a esto:
app.post(‘/setSolicitud’, user.setSolicitud);
Ya sabemos qué hacer en caso de recibir un POST a esta ruta. Lo
siguiente es defi nir la función setSolicitud() en el archivo user.js, del
modo que mostramos a continuación:
CON UN CÓDIGO 201
EN LA RESPUESTA
QUITAMOS AL
USUARIO DE
LA LISTA
SISTEMAS WEB ESCALABLES 33
www.redusers.com
// registramos y envia una solicitud de amistad
exports.setSolicitud = function(req, res){
var uid = req.param(‘uid’);
if (uid.length < 1){
res.send({codigo: 204, mensaje: ‘Usuario Invalido’ });
}else{
// agregamos el usuario a la lista de solicitudes
db.sadd(‘uid:’ + uid + ‘:solicitudes’, req.session.usr.uid);
// obtenemos la solicitudes y la enviamos al usuario
getSolicitudes(uid);
res.send({codigo: 201, mensaje: ‘’ });
}
}
En el código, recibimos el uid del usuario receptor, al que le
agregamos el usuario actual en la clave solicitudes. Luego invocamos el
método getSolicitudes() con el uid del usuario receptor como parámetro
para obtener las solicitudes pendientes del usuario. Crearemos la
función getSolicitudes()en el mismo archivo de la siguiente manera:
// obtenemos la cantidad de solicitudes pendientes
var getSolicitudes = function(uid){
db.smembers(‘uid:’ + uid + ‘:solicitudes’, function (err, solicitudesRecibidas) {
if (solicitudesRecibidas) {
var solicitudes = [];
// obtenemos los datos de cada uid
solicitudesRecibidas.forEach(function(id){
db.hgetall(‘uid:’ + id, function (err, usuario) {
solicitudes.push({‘uid’: id,
‘nombre’ : usuario.nombre,
‘apellido’ : usuario.apellido
});
APÉNDICE. DESARROLLO DE UNA RED SOCIAL34
www.redusers.com
// emitimos las solicitudes
if (solicitudes.length == solicitudesRecibidas.length)
io.sockets.socket(usrEnLinea[uid]).emit(‘getSolicitudes’, solicitudes);
});
});
}
});
}
En el código anterior verifi camos las solicitudes pendientes del
uid pasado por parámetro y, por cada una de ellas, obtenemos la
información. Al obtener todas las solicitudes, emitimos el evento
getSolicitudes con la lista completa al usuario recibido. Luego, al fi nal
del archivo, exportamos la función:
exports.getSolicitudes = getSolicitudes;
Necesitamos capturar el evento getSolicitudes del lado del cliente;
por lo tanto, agregamos lo siguiente a la función $(document).ready() del
archivo script.js:
// io - cuando se recibe una solicitud
sockets.on(‘getSolicitudes’, mostrarSolicitudes);
Cuando ocurra getSolicitudes, vamos a ejecutar la función
mostrarSolicitudes(), que debemos defi nir como sigue:
Si bien actualmente Express se encuentra en la versión 3, debido a que se han creado miles de apli-
caciones con la versión 2 fue necesario dejar la documentación accesible para consultas de desarro-
lladores que necesitan mantener sistemas escritos con esta versión. Para ver la documentación corres-
pondiente a Express en su versión 2, deberemos acceder a la página que se encuentra en la dirección
http://expressjs.com/2x.
DOCUMENTACIÓN DE EXPRESS 2X
SISTEMAS WEB ESCALABLES 35
www.redusers.com
// se muestran las solicitudes de amistad
function mostrarSolicitudes(solicitudes) {
if (solicitudes.length) {
$(‘#solicitudes’).html(‘’);
solicitudes.forEach(function (usuario) {
var contenido = ‘’;
contenido += ‘<li uid=”’ + usuario.uid +’” >’;
contenido += ‘ <span>’ + usuario.nombre + ‘ ‘ + usuario.apellido + ‘ quiere
ser tu amigo!’ + ‘</span>’;
contenido += ‘ <div>’;
contenido += ‘ <input type=”button” value=”Aceptar” uid=”’ + usuario.
uid +’” />’;
contenido += ‘ <input type=”button” value=”Cancelar” uid=”’ + usu-
ario.uid +’” />’;
contenido += ‘ </div>’;
contenido += ‘</li>’;
$(‘#solicitudes’).prepend(contenido);
});
$(‘#valorContador’).fadeOut(500, function() {
$(this).html(solicitudes.length).fadeIn(500)
});
}
}
En el código que mostramos arriba
recibimos las solicitudes y, por cada una
de ellas, creamos un ítem en la lista con
el nombre del usuario que ha enviado la
solicitud original. Además, se procede a agregar
los botones que necesitamos para aceptarla
y también para cancelarla. Posteriormente,
incrementamos el valor del contador con la
cantidad de solicitudes que han sido recibidas.
Cuando un usuario reciba una solicitud de
amistad, verá incrementado el contador.
POR CADA SOLICITUD
DE AMISTAD RECIBIDA
CREAMOS UN ÍTEM
EN LA LISTA DEL
USUARIO CONECTADO
APÉNDICE. DESARROLLO DE UNA RED SOCIAL36
www.redusers.com
Figura 9. Por cada solicitud de amistadrecibida, el contador incrementará su valor.
Debemos lograr que, cuando el usuario haga clic en el contador,
se muestre la ventana con las solicitudes; para lograrlo, agregamos
lo siguiente al método $(document).ready() del archivo script.js:
// contador de solicitudes
$(document).on(‘click’, ‘.contador’, function(){
$(‘#solicitudes’).fadeToggle(500);
});
En el código, mostramos la lista de solicitudes con un efecto de
aparición suave y, en caso de que la lista ya esté visible, la ocultamos.
Sockt.IO es una librería en lenguaje JavaScript, pero también es posible utilizar otras versiones, escri-
tas en diferentes lenguajes, como Java, Objective-C, C, C++, Go, Python y PHP, entre otros. Esto es,
sin duda, una gran ventaja, ya que es posible desarrollar sistemas basados en lenguajes como PHP en
tiempo real. Podemos ver la lista en https://github.com/learnboost/socket.io/wiki.
SOCKET.IO EN OTROS LENGUAJES
SISTEMAS WEB ESCALABLES 37
www.redusers.com
Figura 10. Al recibir una solicitudel usuario puede optar por aceptarla o cancelarla.
Antes defi nimos que, cuando se envía una solicitud de amistad, el
usuario la recibe en tiempo real. Ahora debemos asegurarnos de que
el usuario no conectado la reciba al conectarse. Para esto, agregamos
el código siguiente en el archivo index.js, en el método sessionIO.on(),
dentro del condicional de sesión:
// obtenemos las solicitudes pendientes
user.getSolicitudes(session.usr.uid);
Con este código, cada vez que se conecta, el usuario puede obtener
el listado de las solicitudes pendientes.
En el repositorio ofi cial de Socket.IO hay una lista con varios sistemas que pueden servirnos como base
para el desarrollo de algún proyecto en tiempo real. Un ejemplo muy útil es collabshot, una herramienta
colaborativa de edición de imágenes, notas y chat. Podemos acceder a la lista de proyectos a través
del siguiente enlace: https://github.com/LearnBoost/Socket.IO/wiki/Projects-using-Socket.IO.
PROYECTOS USANDO SOCKET.IO
APÉNDICE. DESARROLLO DE UNA RED SOCIAL38
www.redusers.com
Respuesta a una solicitud de amistadVamos a defi nir las acciones que puede tomar el usuario cuando
recibe una solicitud de amistad. Primero, agregamos lo siguiente al
método $(document).ready() del archivo script.js:
// boton de aceptar/cancelar solicitudes
$(document).on(‘click’, ‘#solicitudes input[type=”button”]’, function(){
setRespuestaSolicitud($(this));
});
En este código capturamos el evento click para aceptar o cancelar una
solicitud, mediante el cual ejecutamos la función setRespuestaSolicitud(),
que debemos defi nir en el mismo archivo del siguiente modo:
// se responde una solicitud de amistad
function setRespuestaSolicitud(boton) {
$.post(“/setRespuestaSolicitud”, ‘uid=’ + boton.attr(‘uid’) + ‘&accion=’ + boton.
attr(‘value’),
function(respuesta){
if (respuesta.codigo === 201){
$(‘#solicitudes li[uid=”’ + boton.attr(‘uid’) + ‘”]’).fadeOut(1000);
$(‘#valorContador’).fadeOut(500, function() {
$(this).html(parseInt($(this).html()) - 1).fadeIn(500)
});
}else
$(‘#solicitudes li[uid=”’ + boton.attr(‘uid’) + ‘”]’).html(respuesta.mensaje);
}
);
}
En el código anterior, enviamos un POST por Ajax a la ruta
/setRespuestaSolicitud, con el uid del usuario al cual se responde la
solicitud y con la acción (es decir, Aceptar o Cancelar). Cuando se recibe la
respuesta, si se obtiene el código 201 se elimina el nombre del usuario y
se descuenta el contador. De ocurrir lo contrario, se muestra el mensaje.
SISTEMAS WEB ESCALABLES 39
www.redusers.com
Lo siguiente es habilitar el ruteo para /setRespuestaSolicitud, para lo cual
descomentamos la siguiente línea en el archivo app.js, pasando de esto:
//app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud);
al código que presentamos a continuación:
app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud);
Necesitaremos defi nir la función setRespuestaSolicitud en el archivo
user.js, de la siguiente manera:
// respondemos la solicitud de amistad
exports.setRespuestaSolicitud = function(req, res){
var uid = req.param(‘uid’),
accion = req.param(‘accion’);
db.get(‘uid:’ + uid , function (err, reply) {
if (reply === null){
res.send({codigo: 204, mensaje: ‘Usuario Invalido’ });
}else{
if (accion == ‘Aceptar’){
// agregamos como amigo a ambos usuarios
db.sadd(‘uid:’ + uid + ‘:amigos’, req.session.usr.uid);
db.sadd(‘uid:’ + req.session.usr.uid + ‘:amigos’, uid);
}
// eliminamos la solicitud actual
db.srem(‘uid:’ + req.session.usr.uid + ‘:solicitudes’, uid);
res.send({codigo: 201, mensaje: ‘’ });
}
});
}
En este código, verifi camos si existe el usuario recibido y, en
caso de que se haya aceptado la solicitud, se agregan como amigos
APÉNDICE. DESARROLLO DE UNA RED SOCIAL40
www.redusers.com
mutuamente a través de la clave amigos. Luego se elimina la solicitud
en ambos casos, es decir, tanto si se ha aceptado la solicitud como si
no se ha hecho.
Figura 11. Una solicitud de amistadpermanecerá en la lista hasta que el usuario realice
una de las dos acciones defi nidas.
Lista de amigos conectadosVamos a crear una función para obtener los amigos que están
conectados cuando un usuario inicia sesión. Para hacerlo, agregamos
el código siguiente en el archivo llamado index.js, en la función
sessionIO.on(), dentro del condicional de sesión:
// obtenemos los usuarios conectados
user.getAmigosConectados(session.usr.uid);
En el código defi nimos que, cuando un usuario se conecta, se ejecuta
la función getAmigosConectados(). Por lo tanto, vamos a defi nirla en el
archivo user.js de la siguiente manera:
SISTEMAS WEB ESCALABLES 41
www.redusers.com
// obtenemos los amigos conectados
var getAmigosConectados = function(uid){
db.smembers(‘uid:’ + uid + ‘:amigos’, function (err, amigos) {
if (amigos) {
var usrConectados = [];
var i = 0;
amigos.forEach(function(id){
i++;
// verifi camos que el usuario se encuentre conectado
if (usrEnLinea[id]){
// obtenemos la informacion del usuario
db.hgetall(‘uid:’ + id, function (err, usuario) {
usrConectados.push({‘uid’: id,
‘nombre’ : usuario.nombre,
‘apellido’ : usuario.apellido
});
// emitimos los amigos
if (i == amigos.length)
io.sockets.socket(usrEnLinea[uid]).emit(‘setAmigosConectados’, usrCo-
nectados);
});
}
});
}
});
}
Fred Sarmento ha creado un portal con recursos para los frontend, donde se incluyen librerías y plugins
como jQuery, Normalize.css, herramientas de debug y testeo como Firebug y Chrome Developer Tools,
tutoriales en línea, y editores de código como Sublime Text3. Podemos ver la lista completa en el siguien-
te enlace: http://fredsarmento.me/frontend-tools.
HERRAMIENTAS PARA FRONTEND
APÉNDICE. DESARROLLO DE UNA RED SOCIAL42
www.redusers.com
En el código anterior hemos obtenido los amigos del usuario actual
y, para cada uno, verifi camos si se encontraba conectado, es decir que
existiera en el array usrEnLinea. De estos usuarios hemos obtenido el
uid, el nombre y el apellido. Cuando terminamos de obtener los amigos,
emitimos el evento setAmigosConectados con la lista.
En este punto debemos exportar la función para que esté disponible
desde cualquier lugar del sistema. Para ello, agregamos el siguiente
código al fi nal del archivo llamado user.js:
exports.getAmigosConectados = getAmigosConectados;
Vamos a capturar este evento del lado del cliente, defi niendo lo que
sigue en el archivo script.js dentro del método $(document).ready():
// io - mostramos los amigos conectados
sockets.on(‘setAmigosConectados’, mostrarAmigosConectados);
Hasta este momento hemos defi nido que, cuando se recibe el evento
setAmigosConectados, se ejecuta la función mostrarAmigosConectados(); por
lo tanto, debemos crearla en el mismo archivo:
// muestra los amigos conectados
function mostrarAmigosConectados(data){
if (data.length) {
$(‘#usuariosAmigos’).html(‘<ul></ul>’);
data.forEach(function (usuario) {
var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido;
$(‘#usuariosAmigos ul’).prepend(‘<li uid=”’ + usuario.uid +’”>’ + nombreUs-
uario + ‘</li>’);
});
};
}
Con este código obtenemos la lista de amigos conectados, agregando
a cada uno de ellos en el contenedor #usuariosAmigos.
SISTEMAS WEB ESCALABLES 43
www.redusers.com
Figura 12. Cuando los usuarios inicien sesión veránen la columna izquierda la lista de los amigos conectados.
Informar cuandose conecta un usuario
Vamos a crear una función para informar a los amigos, en tiempo
real, que un usuario se ha conectado. Primero nos encargamos de
agregar el siguiente código en el archivo denominado index.js, en la
función sessionIO.on(), dentro del condicional de sesión:
// informamos a los amigos que se ha conectado el usuario
user.setAmigoConectado(session.usr);
Según los últimos informes de Cisco, para el año 2017 habrá cerca de 3.600 millones de usuarios de
Internet, esto se puede resumir como casi el 50% de la población mundial. Implicaría un aumento del
tráfi co mundial por tres, donde el servicio será accesible desde notebooks, netbooks, smartphones,
tablets y televisores inteligentes.
INTERNET
APÉNDICE. DESARROLLO DE UNA RED SOCIAL44
www.redusers.com
En el código anterior defi nimos que, cuando un usuario se conecte,
se ejecutará la función setAmigoConectado(), a la que le pasamos como
parámetro todos los datos del usuario actual.
En este momento hacemos la defi nición de la función
setAmigoConectado() en el archivo user.js:
// informamos a los amigos que se ha conectado el usuario
var setAmigoConectado = function(usr){
// obtenemos todos los amigos
db.smembers(‘uid:’ + usr.uid + ‘:amigos’, function (err, amigos) {
amigos.forEach(function(id){
// verifi camos que el usuario se encuentre conectado
if (usrEnLinea[id])
io.sockets.socket(usrEnLinea[id]).emit(‘setAmigoConectado’, usr);
});
});
}
En el código anterior, obtenemos los amigos del usuario actual de
la base de datos y, a cada uno de ellos, le informamos los datos del
usuario mediante el evento setAmigoConectado. Luego, exportamos la
función para que esté disponible en todo el sistema:
exports.setAmigoConectado = setAmigoConectado;
Vamos a capturar el evento del lado del cliente mediante el siguiente
código, en el $(document).ready() del archivo script.js:
// io - mostramos cuando se conecta un amigo
sockets.on(‘setAmigoConectado’, mostrarAmigoConectado);
En el código anterior, defi nimos que se ejecutará la función
mostrarAmigoConectado() cuando se reciba el evento setAmigoConectado.
Por lo tanto, la defi nimos en el mismo archivo:
SISTEMAS WEB ESCALABLES 45
www.redusers.com
// agrega un usuario a la lista de amigos cuando se conecta
function mostrarAmigoConectado(data){
if ($(‘#usuariosAmigos ul li[uid=”’ + data.uid + ‘”]’).size() === 0){
// creamos la lista si no existe
if($(‘#usuariosAmigos p’).size()){
$(‘#usuariosAmigos p’).remove();
$(‘#usuariosAmigos’).append(‘<ul></ul>’);
}
var nombreUsuario = data.nombre + ‘ ‘ + data.apellido;
$(‘#usuariosAmigos ul’).prepend(‘<li uid=”’ + data.uid +’”>’ + nombreUsuario
+ ‘</li>’);
}
}
En este código nos encargamos de verifi car que el usuario recibido
no exista en la lista de amigos y lo agregamos. También podemos
corroborar si existe el elemento <p>, para eliminarlo en caso de que
sea el primer amigo que se muestre en la lista.
Informar cuandose desconecta un usuario
Vamos a crear una función para informar a los amigos cuando un
usuario se desconecta. Si observamos home.ejs veremos que hemos
defi nido el enlace salir y apunta a la ruta /salir. Para que nuestro sistema
pueda efectuar alguna acción cuando recibe esta ruta, necesitaremos
descomentar la siguiente línea del archivo app.js, pasando de esto:
//app.get(‘/salir’, user.logout);
a esto:
app.get(‘/salir’, user.logout);
APÉNDICE. DESARROLLO DE UNA RED SOCIAL46
www.redusers.com
En este código pudimos defi nimos que, cuando se solicite la ruta
/salir, se deberá ejecutar la función logout(). Por lo tanto, debemos
proceder a crearla en el archivo llamado user.js:
// logout
exports.logout = function(req, res){
// informamos a los amigos que se ha desconectado el usuario
setAmigoDesconectado(req.session.usr.uid);
// eliminamos el uid del array de usuarios y la session
delete usrEnLinea[req.session.usr.uid];
// eliminamos la clave se session
req.session.usr = ‘’;
// redireccionamos
res.redirect(‘/’);
}
En el código nos encargamos de ejecutar la función
setAmigoDesconectado(), que vamos a crear a continuación; luego,
borramos el usuario del array de usuarios en línea, vaciamos la
variable de sesión y redirigimos al usuario a la pagina inicial.
A continuación vamos a defi nir la función setAmigoDesconectado()
en el mismo archivo con el que estamos trabajando:
// informamos a los amigos que se ha desconectado el usuario
var setAmigoDesconectado = function(uid){
// obtenemos todos los amigos
db.smembers(‘uid:’ + uid + ‘:amigos’, function (err, amigos) {
amigos.forEach(function(id){
// verifi camos que el usuario se encuentre conectado
if (usrEnLinea[id])
io.sockets.socket(usrEnLinea[id]).emit(‘setAmigoDesconectado’, uid);
});
});
}
SISTEMAS WEB ESCALABLES 47
www.redusers.com
Aquí hemos obtenido todos los amigos del usuario y, para cada uno
de ellos, hemos verifi cado si estaba conectado, emitiendo el evento
setAmigoDesconectado con el uid del usuario actual.
Para continuar nos encargamos de exportar la función para que esté
disponible desde cualquier lugar del sistema:
exports.setAmigoDesconectado = setAmigoDesconectado;
Ahora necesitaremos realizar la captura del evento desde el lado
del cliente. Para efectuar esta tarea debemos proceder a escribir lo
que mostramos a continuación, en el método denominado $(document).
ready() dentro del archivo script.js:
// io - mostramos cuando se desconecta un amigo
sockets.on(‘setAmigoDesconectado’, mostrarAmigoDesconectado);
Con el código establecemos que, cuando se reciba el
evento setAmigoDesconectado, se deberá ejecutar la función
mostrarAmigoDesconectado(), que defi niremos en el mismo archivo:
// elimina un usuario de la lista de amigos cuando se desconecta
function mostrarAmigoDesconectado(data){
// eliminamos el amigo de la lista
$(‘#usuariosAmigos ul li[uid=”’ + data + ‘”]’).remove();
// si no hay amigos mostramos el mensaje
if ($(‘#usuariosAmigos ul li’).size() == 0){
$(‘#usuariosAmigos ul’).remove();
$(‘#usuariosAmigos’).prepend(‘<p>No hay amigos conectados...</p>’);
}
// eliminamos la ventana de chat si existe
if ($(‘#ventana-’ + data).size())
$(‘#ventana-’ + data).remove();
}
APÉNDICE. DESARROLLO DE UNA RED SOCIAL48
www.redusers.com
Eliminamos el ítem de la lista de amigos y verifi camos si existen
otros conectados; en el caso contrario mostramos un elemento <p>
con el mensaje de que no hay amigos conectados y, luego, verifi camos
si existe una ventana de chat abierta y la borramos. A continuación
trabajaremos en el chat con amigos.
Creación del sistema de chatPara continuar vamos a desarrollar el sistema de chat entre los
amigos que están conectados. La idea es que, al hacer clic en un amigo
de la lista, se abra una ventana típica de chat.
Primero vamos a capturar el evento click. En el método $(document).
ready(), en el archivo script.js, agregamos lo siguiente:
// abre ventanas de chat
$(document).on(‘click’, ‘#usuariosAmigos ul li’, function(){
abrirVentanaChat($(this).attr(‘uid’));
});
Así, defi nimos que, cuando se haga clic en un elemento de la lista,
se ejecutará la función abrirVentanaChat(), y pasamos como parámetro
el atributo uid del usuario de la lista.
A continuación debemos proceder a defi nir la función denominada
abrirVentanaChat() en el mismo archivo:
// abre una ventana de chat
function abrirVentanaChat(uid) {
if (!$(‘#ventana-’ + uid).size()){
var nombre = $(‘#usuariosAmigos ul li[uid=”’ + uid + ‘”]’).html();
var ventana = ‘<div class=”ventana marco celeste” id=”ventana-’ + uid + ‘”
uid=”’ + uid + ‘”>’;
ventana += ‘ <span>’+ nombre + ‘</span>’;
ventana += ‘ <textarea cols=”20” rows=”7” readonly=”true”></tex-
tarea>’;
SISTEMAS WEB ESCALABLES 49
www.redusers.com
ventana += ‘ <input type=”text” class=”chat-text” maxlength=”20” />’;
ventana += ‘</div>’;
$(‘.chat’).append(ventana);
}
}
En el código, obtenemos el uid del usuario con
el que se va a chatear, y se crea una ventana que
contendrá un elemento <input> para escribir y un
elemento <textarea> para mostrar los mensajes;
agregamos toda la ventana al elemento chat.
Vamos a defi nir el evento que va a capturar el
mensaje para enviarlo al receptor. Para hacerlo,
agregamos el siguiente código en el método
$(document).ready() del mismo archivo:
// cuando el usuario presiona enter emite el mensaje
$(document).on(‘keypress’, ‘.chat-text’, function(e){
if (e.which == 13)
enviarMensaje($(this).parent().attr(‘uid’), $(this).val());
});
En este código simplemente ejecutamos la función enviarMensaje()
cuando se presiona la tecla ENTER, pasándole como parámetros el uid
del usuario receptor y el mensaje.
Debemos defi nir la función enviarMensaje() en el mismo archivo:
Además de las herramientas que vienen integradas por defecto en Express, integrantes de la comunidad
ofi cial han creado decenas de extensiones muy útiles para ser utilizadas en los desarrollos y ahorrar
tiempo de programación. Podemos conocerlas accediendo al siguiente enlace: https://github.com/
senchalabs/connect/wiki.
EXTENSIONES PARA EXPRESS
LUEGO DE OBTENER
EL UID DEL USUARIO
SE CREA UNA
VENTANA APTA PARA
UNA SESIÓN DE CHAT
APÉNDICE. DESARROLLO DE UNA RED SOCIAL50
www.redusers.com
// se envia un mensaje en el chat
function enviarMensaje(uid, msg) {
var data = { para : uid,
mensaje: msg,
fecha : new Date()
};
// emitimos el mensaje
sockets.emit(‘enviarMensaje’, data);
// agregamos el mensaje al textarea
$(‘#ventana-’+ uid +’ textarea’).val($(‘#ventana-’+ uid +’ textarea’).val() + ‘yo:
‘ + msg + ‘\r\n’);
// limpiamos la caja de texto
$(‘#ventana-’+ uid +’ .chat-text’).val(‘’);
}
En el código, creamos un objeto con la clave para (donde le
asignamos el uid receptor, el mensaje y la fecha de emisión) y luego
emitimos el mensaje enviarMensaje al servidor; por último, agregamos
a <textarea> el mensaje y limpiamos la caja de texto.
Ahora necesitamos capturar el evento del lado del servidor. Debemos
agregar lo que sigue en el archivo index.js, en la función sessionIO.on()
dentro del condicional de sesión:
// Emitimos el mensaje al usuario
socket.on(‘enviarMensaje’, function (data){
data.de = session.usr.uid;
data.nombre = session.usr.nombre + ‘ ‘ + session.usr.apellido;
user.enviarMensaje(data);
});
En el código hemos capturado el evento enviarMensaje y hemos
agregado la clave de con el uid del usuario actual, y la clave nombre
con los atributos nombre y apellido concatenados. Ejecutamos la función
enviarMensaje() pasándole como parámetro el objeto recibido.
Lo que sigue es crear la función enviarMensaje() en el archivo user.js:
SISTEMAS WEB ESCALABLES 51
www.redusers.com
// se envia el mensaje del chat
var enviarMensaje = function (data){
io.sockets.socket(usrEnLinea[data.para]).emit(‘mensajeRecibido’, data);
}
Simplemente, enviamos el objeto recibido al usuario receptor
a través del evento mensajeRecibido. Luego, exportamos la función
para que esté disponible desde cualquier lugar del sistema:
exports.enviarMensaje = enviarMensaje;
Necesitamos capturar este evento del lado del cliente, dentro de la
función $(document).ready():
// io - mostramos el mensaje recibido del chat
sockets.on(‘mensajeRecibido’, mostrarMensajeRecibido);
En este código hemos defi nido que, cuando se recibe el evento
mensajeRecibido, se ejecuta la función mostrarMensajeRecibido(), por lo
que debemos defi nirla en el mismo archivo. Antes de la función vamos
a establecer una variable global llamada ultimaFechaMsg:
var ultimaFechaMsg = 0;
// se muestra un mensaje recibido del chat
function mostrarMensajeRecibido(data){
// si se recibe mensajes duplicados
if (ultimaFechaMsg == data.fecha)
return;
else{
// actualizamos la fecha del ultimo mensahe recibido
ultimaFechaMsg = data.fecha;
// si no existe la ventana la creamos
if ($(‘#ventana-’ + data.de).size() == 0)
abrirVentanaChat(data.de);
APÉNDICE. DESARROLLO DE UNA RED SOCIAL52
www.redusers.com
// agregamos la informacion del mensaje
$(‘#ventana-’ + data.de + ‘ span’).html(data.nombre);
$(‘#ventana-’ + data.de + ‘ textarea’).val($(‘#ventana-’ + data.de + ‘ textar-
ea’).val() + data.nombre + ‘: ‘ + data.mensaje + ‘\r\n’);
}
}
En el código anterior, declaramos una variable global que
utilizaremos en la función para comparar la fecha de recepción de
mensajes en el caso de que existan mensajes duplicados. Luego, si
la ventana de chat no existe, la abrimos y agregamos el mensaje.
Figura 13. En la imagen podemos ver la ventana de chat con amigos.
StatCounter es un sitio que publica estadísticas globales acerca de diferentes tecnologías. Entre las opcio-
nes que ofrece se encuentra la posibilidad de ver qué tecnología comparar, permite seleccionar el tipo de
gráfi co (líneas, barras o mapa) y además permite descargar el gráfi co en formato JPG o CSV. Podemos
conocerlo mejor a través del siguiente enlace: http://gs.statcounter.com.
ESTADÍSTICAS MEDIANTE STATCOUNTER
SISTEMAS WEB ESCALABLES 53
www.redusers.com
Debemos destacar que el sistema de chat desarrollado permite al
usuario tener múltiples conversaciones en simultáneo, ya que en el
servidor mantenemos el id del Socket que identifi ca a cada uno.
Figura 14. Mediante el sistema de chat desarrollado,los usuarios pueden mantener varias conversaciones a la vez.
Creación del sistema de postsVamos a desarrollar el sistema de publicación de post con amigos.
Primero necesitamos poder enviar al servidor los posts escritos, para
lo cual debemos capturar el contenido del formulario. En el archivo
script.js, dentro del método $(document).ready(), escribiremos lo siguiente:
// nuevo post
$(document).on(‘submit’, ‘#postFrm’, function(e){
e.preventDefault();
setPost($(this));
});
Así, defi nimos que, cuando se envía el formulario, se ejecuta la
función setPost() enviando como parámetro el mismo objeto.
A continuación defi nimos la función setPost() en el mismo archivo:
APÉNDICE. DESARROLLO DE UNA RED SOCIAL54
www.redusers.com
// metodo POST cuando se escrine un post nuevo
function setPost(form) {
$.post(“/setPost”, form.serialize(),
function(respuesta){
if (respuesta.codigo === 201){
var bloque = ‘<article class=”bloque marco blanco”>’;
bloque += ‘ <div class=”header”>’;
bloque += ‘ <img src=”/img/icon-profi le.png” />’;
bloque += ‘ <span class=”bloque_usuario”>yo </span>’;
bloque += ‘ <span class=”bloque_fecha”> hace unos segundos...</
span>’;
bloque += ‘ </div>’;
bloque += ‘ <div class=”value”>’ + $(‘#postFrm textarea’).val() + ‘</
div>’;
bloque += ‘</article>’;
$(‘#posts’).prepend(bloque);
$(‘#postFrm textarea’).val(‘’);
}else
mostrarMensajeFormulario(form, respuesta.mensaje);
}
);
}
En el código hacemos un POST por Ajax a la ruta /setPost con los
elementos del formulario serializado. Si la respuesta es 201, creamos
un bloque con el post escrito y se lo mostramos al mismo usuario.
En el caso contrario, mostramos el mensaje de error. Como estamos
haciendo un POST descomentamos la siguiente línea en app.js. De esto:
//app.post(‘/setPost’, user.setPost);
debemos pasar a lo que mostramos a continuación:
app.post(‘/setPost’, user.setPost);
SISTEMAS WEB ESCALABLES 55
www.redusers.com
En este código nos encargamos de defi nir que el ruteo deberá
ejecutar la función setPost(). Por esta razón, tendremos que realizar
esta defi nición en el archivo denominado user.js:
// registramos el post para el usuario actual y los amigos
exports.setPost = function(req, res){
var post = req.param(‘post’);
if (post.length < 1){
res.send({codigo: 204, mensaje: ‘Mensaje Invalido’ });
return;
}else{
db.incr(‘global:ultimoPid’, function (err, pid) {
var fecha = formatearFecha();
var uid = req.session.usr.uid;
// seteamos el post y la fecha/hora actual
db.hmset(‘post:’ + pid, {‘uid’ : uid,
‘fecha’ : fecha,
‘post’ : post
}
);
// incremento la cantidad de posts para el usuario actual
db.incr(‘uid:’ + uid + ‘:nposts’);
var postID = pid;
// obtenemos los amigos
db.smembers(“uid:” + uid + “:amigos”, function (err, amigos) {
// agrego el usuario actual
amigos.push(uid);
// agrego el id del post a cada amigo
amigos.forEach(function(sid){
db.lpush(‘uid:’ + sid + ‘:posts’, postID);
});
});
APÉNDICE. DESARROLLO DE UNA RED SOCIAL56
www.redusers.com
res.send({codigo: 201, mensaje: ‘Mensaje publicado’});
});
}
};
En el código anterior, recibimos el post y verifi camos que tenga
contenido; luego, incrementamos la clave ultimoPid y llamamos a
la función formatearFecha() para obtener la fecha actual formateada.
Después, obtenemos el usuario actual y guadamos en la base de datos
el post con la información obtenida. Seguidamente, nos encargamos de
incrementar la cantidad de posts para este usuario y la guardamos en
simultáneo para cada uno de los amigos.
A continuación vamos a defi nir la función formatearFecha():
// funcion para formatear la fecha
function formatearFecha(fecha) {
var d = new Date(fecha || Date.now()),
dia = d.getDate(),
mes = (d.getMonth() + 1),
anio = d.getFullYear(),
hora = d.getHours(),
minuto = d.getMinutes(),
segundo = d.getSeconds();
if (mes.length < 2) mes += ‘0’;
if (dia.length < 2) dia += ‘0’;
return [dia, mes, anio].join(‘-’) + ‘ ‘ + [hora, minuto, segundo].join(‘:’);
}
En el código anterior hemos obtenido la fecha actual, en el caso de
que no la pasemos por parámetro, y la formateamos de manera legible.
Lo que nos queda, ahora, es obtener los posts cuando el usuario inicia
sesión. Para esto, agregamos el siguiente código en el archivo index.js,
en la función sessionIO.on(), dentro del condicional de sesión:
SISTEMAS WEB ESCALABLES 57
www.redusers.com
// obtenemos los posts
user.getPosts(session.usr.uid);
De este modo defi nimos que, al conectarse un usuario, se ejecutará
la función getPosts(), a la cual le pasaremos como parámetro el uid
actual. Debemos defi nir la función que corresponde en el archivo
llamado user.js de la siguiente manera:
var getPosts = function(uid){
db.lrange(‘uid:’ + uid + ‘:posts’, 0, 10, function (err, posts) {
if (posts) {
var arrayPosts = [];
var i = 0;
// obtenemos los atributos de cada post
posts.forEach(function(pid) {
db.hgetall(‘post:’ + pid, function (err, post) {
var usuarioNombre;
// obtenemos los atributos del usuario
db.hgetall(‘uid:’ + post.uid, function (err, usuario) {
i++;
arrayPosts.push({‘uid’ : post.uid,
‘nombre’ : usuario.nombre,
‘apellido’: usuario.apellido,
‘fecha’ : post.fecha,
‘mensaje’ : post.post
});
// al fi nal de la lista de post se emite al usuario
if (i == posts.length)
io.sockets.socket(usrEnLinea[uid]).emit(‘setPosts’, arrayPosts);
});
});
});
}
});
}
APÉNDICE. DESARROLLO DE UNA RED SOCIAL58
www.redusers.com
Hemos obtenido los últimos diez posts del usuario actual y, de cada
uno de ellos, la información completa y del usuario que la escribió, que
almacenamos en un array. Por último, debemos realizar la emisión del
evento llamado setPosts con el array creado.
A continuación debemos capturar el evento setPosts del lado del
cliente. Para realizar esto agregaremos lo que sigue en el archivo
script.js en el método $(document).ready():
// io - mostramos los posts
sockets.on(‘setPosts’, mostrarPosts);
Así determinamos que, cuando ocurra el evento setPosts,
ejecutaremos la función mostrarPosts(), que defi niremos en el mismo
archivo tal como mostramos:
// muestra los posts en el contenedor
function mostrarPosts(data){
if (data.length) {
$(‘#posts’).html(‘’);
data.forEach(function (post) {
var nombreUsuario = post.nombre + ‘ ‘ + post.apellido;
var bloque = ‘<article class=”bloque marco blanco”>’;
bloque += ‘ <div class=”header”>’;
bloque += ‘ <img src=”/img/icon-profi le.png” />’;
bloque += ‘ <span class=”bloque_usuario”>’ + nombreUsuario + ‘</
span>’;
bloque += ‘ <span class=”bloque_fecha”>’+ post.fecha + ‘</span>’;
bloque += ‘ </div>’;
bloque += ‘ <div class=”value”>’ + post.mensaje + ‘</div>’;
bloque += ‘</article>’;
$(‘#posts’).append(bloque);
});
};
}
SISTEMAS WEB ESCALABLES 59
www.redusers.com
En el código nos hemos encargado de obtener
los posts correspondientes y, por cada uno de
ellos, creamos un bloque con el usuario que lo
escribió, la fecha y el mensaje, y lo agregamos
al contenedor #posts.
Por último, recordemos que había quedado
pendiente explicar la llamada a la función user.
getTotalUsuarios() del archivo index.js, en caso de
que el usuario haya iniciado sesión. Ya hemos
desarrollado esta función, que simplemente
realiza la devolución del total de usuarios registrados en el sistema
(debemos considerar que serán mostrados en la parte inferior
de la pantalla principal del sistema).
Figura 15. En la imagen podemosobservar la apariencia de los posts y que el usuario
tiene acceso a los posts de sus amigos.
Vista de la base de datosInspeccionando la base de datos de nuestro sistema podemos
ver la estructura generada; por ejemplo, los usuarios registrados y
conectados, las listas de amigos generadas para cada usuario, los posts
creados por los usuarios y también los contadores.
POR CADA POST SE
CREA UN BLOQUE
CON EL USUARIO QUE
LO ESCRIBIÓ Y OTROS
DATOS IMPORTANTES
APÉNDICE. DESARROLLO DE UNA RED SOCIAL60
www.redusers.com
Figura 16. Cada usuario contiene, además de sus datos,una lista de amigos, una lista de posts y un contador de posts.
También podemos observar qué información tenemos de cada
post. Entre los datos que es posible identifi car para los post escritos
encontramos el autor, la fecha en que se creó y también el mensaje
que corresponde, así como el identifi cador del usuario.
Figura 17. En la base de datos guardamos la fecha,el mensaje y el identifi cador del usuario que lo escribió.
SISTEMAS WEB ESCALABLES 61
www.redusers.com
Hasta aquí hemos realizado el desarrollo completo de un sistema
medianamente complejo, que nos sirve como base para cualquier tipo
de sistema escalable con funciones y características en tiempo real.
Como sabemos, una red social cumple los requisitos mencionados, lo
que la convierte en el ejemplo ideal para el tema que hemos tratado a
lo largo de los capítulos que componen esta obra.
Hemos aplicado todos los conocimientos y temas tratados a lo largo del libro mediante el desarrollo de
una red social que propone un gran cambio en la manera de pensar las acciones, ya que la mayoría de las
interacciones deben ser refl ejadas en tiempo real y desencadenan un efecto en los demás usuarios. Las
bases de datos NoSQL, como Redis, cuentan con ventaja en la disponibilidad para grandes volúmenes
de datos, ofreciendo un gran desempeño en el funcionamiento de cualquier sistema; al mismo tiempo,
mediante Socket.IO es posible emitir y recibir eventos completamente personalizados, que entregan un
aspecto único a la interacción de los usuarios con los sistemas.
RESUMEN
APÉNDICE. DESARROLLO DE UNA RED SOCIAL62
www.redusers.com
Actividades
Si tiene alguna consulta técnica relacionada con el contenido, puede contactarse con nuestros expertos: profesor@redusers.com
PROFESOR EN LÍNEA
TEST DE AUTOEVALUACIÓN
1 ¿Express ofrece la posibilidad de defi nir un sistema autosufi ciente?
2 ¿Es posible defi nir diferentes entornos de funcionamiento en Express?
3 ¿Con qué característica debe contar una variable para tener un alcance global?
4 ¿ Qué característica debe tener una función para tener un alcance global?
5 ¿Socket.IO maneja sesiones? Justifi car.
6 ¿Es posible utilizar un mecanismo de cookies, en vez de sesiones, para manejar usuarios conectados?
7 ¿on() y emit() son métodos de Socket.IO de Express?
8 ¿Qué utilidad tiene la exportación de las funciones?
9 ¿Los métodos on() y emit() pueden ser utilizados en cliente y en servidor indistintamente?
10 ¿Un sistema desarrollado con Node y Express necesita de un servidor web como Apache?
EJERCICIOS PRÁCTICOS
1 Implemente un buscador para localizar posts, un sistema de comentario con Socket.IO para notifi car a los amigos y un sistema de puntuación para los posts.
2 Instale el módulo nodemailer para enviar correos electrónicos.
3 Genere la función Ver y editar el perfi l actual y Acceder al perfi l de los amigos.
4 Implemente un sistema de cierre de sesión y notifi cación cuando el usuario cierra el navegador.