Let's Code!

8. Aplicación de ejemplo:   

La aplicación de ejemplo es una aplicación en modo consola. En donde probaremos insertar un nuevo registro en la tabla Entidad y veremos que los datos están encriptados en nuestra Base de Datos Sql Server.

using JhonPierre.CriptoEntities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JhonPierre.Output
{
   public class Program
   {
      static void Main(string[] args)
      {
        List<Entidad> lista = new List<Entidad>();
        using (CRIPTO_DATAEntities context = new CRIPTO_DATAEntities())
        {
          //Guardar datos.
          Entidad obj = new Entidad();
          obj.Nombres = "Juan Pedro";
          obj.Apellidos = "Perez Suarez";
          obj.Comentario = "Comentario sin encriptar";
          obj.DatoNoEncriptado = "Dato sin encriptar";
          obj.Telefono = "1020-304050";
          obj.FechaNacimiento = new DateTime(2014, 10, 28, 0, 0, 0).ToString("dd/MM/yyyy");

          Console.WriteLine("Entidad antes de guardar");
          PrintEntidad(obj);

          context.Entidad.Attach(obj);
          context.Entry(obj).State = System.Data.EntityState.Added;
          context.SaveChanges();

          //Leer datos desde BD.
          lista = (from e in context.Entidad
                  select e).ToList();

       }

      foreach (var ent in lista)
      {
         PrintEntidad(ent);
      }

      Console.ReadLine();
   }

   public static void PrintEntidad(Entidad e)
   {
      Console.WriteLine("==========================================================");
      Console.WriteLine("ENTIDAD Nro: " + e.IdEntidad);
      Console.WriteLine("Nombres: " + e.Nombres);
      Console.WriteLine("Apellidos: " + e.Apellidos);
      Console.WriteLine("Teléfono: " + e.Telefono);
      Console.WriteLine("Fecha Nacimiento: " + e.FechaNacimiento);
      Console.WriteLine("Comentario: " + e.Comentario);
      Console.WriteLine("Dato No Encriptado: " + e.DatoNoEncriptado);
   }
 }
}

Ejecución:

data

Eureka! Ahora podemos ver nuestra data encriptada en la base de datos.

6. Encriptando las entidades:   

Entity Framework implementa una clase en donde se administran las entidades, llamado Context. Esta es el objeto principal que  contiene el set de entidades en memoria y permite abrir sesiones para insertar, eliminar, modificar y listar objetos. La clase context implementada, hereda siempre de la clase DbContext.

Esta clase Context, contiene un método principal muy usado, llamado SaveChanges(), encargado de persistir los objetos en memoria hacia la base de datos. Además contiene la funcionalidad inversa, es decir obtener los registros de la base de datos y convertirlos en los objetos en memoria, esto último se implementa a través de un evento llamado ObjectMaterialized.

De modo que para encriptar nuestras entidades, debemos cifrar sus propiedades antes de persistirlos en la base de datos. Y para desencriptarlos debemos descifrar las propiedades en el evento en que se materializa un objeto en memoria a partir de los datos obtenidos de la base de datos.

Para no modificar directamente el código generado de nuestra clase context, haremos uso de la Herencia y crearemos una super clase que contendrá estos cambios para poder encriptar y desencriptar nuestras entidades.

Llamaremos a esta super clase CriptoDBContext.

using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
using System.Reflection;

namespace JhonPierre.Cripto.Entities
{
   public class CriptoDBContext : DbContext
   {

      protected AESCryptoManager CriptoManager;

      public CriptoDBContext(string connString)
      : base(connString)
      {

          this.Configuration.ProxyCreationEnabled = false;
          CriptoManager = new AESCryptoManager();
          var contextAdapter = ((IObjectContextAdapter)this);
          contextAdapter.ObjectContext.ObjectMaterialized += ObjectContext_ObjectMaterialized;
       }

       protected void ObjectContext_ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
       {
          DecryptEntity(e.Entity);
       }

       protected void EncryptEntity(Object obj)
       {
          PropertyInfo[] propiedades = obj.GetType().GetProperties();
          foreach (PropertyInfo prop in propiedades)
          {
             Encrypted[] attributes = (Encrypted[])prop.GetCustomAttributes(typeof(Encrypted), false);
             if (attributes != null && attributes.Length > 0)
             {
                 if (attributes[0].EsEncriptada)
                 {
                   var valueNoEncriptado = prop.GetValue(obj);
                   //encriptar esta propiedad
                   string valueEncriptado = CriptoManager.Encriptar(valueNoEncriptado);
                   prop.SetValue(obj, valueEncriptado);
                  }
             }
             else
             {
                //no encriptar
             }
           }
        }

        protected void DecryptEntity(Object obj)
        {
           PropertyInfo[] propiedades = obj.GetType().GetProperties();
           foreach (PropertyInfo prop in propiedades)
           {
              Encrypted[] attributes = (Encrypted[])prop.GetCustomAttributes(typeof(Encrypted), false);
              if (attributes != null && attributes.Length > 0)
              {
                 if (attributes[0].EsEncriptada)
                 {
                     var valueEncriptado = prop.GetValue(obj);
                     //desencriptar esta propiedad
                     if (valueEncriptado != null)
                     {
                         string valueNoEncriptado = CriptoManager.Desencriptar(valueEncriptado.ToString());
                         prop.SetValue(obj, valueNoEncriptado);
                     }
                     else
                     {
                        prop.SetValue(obj, valueEncriptado);
                     }
                 }
             }
             else
             {
                   //no desencriptar
             }
         }
       }

       public override int SaveChanges()
       {
         var contextAdapter = ((IObjectContextAdapter)this);
         contextAdapter.ObjectContext.DetectChanges();
         var pendingEntities = contextAdapter.ObjectContext.ObjectStateManager
                              .GetObjectStateEntries(EntityState.Added | EntityState.Modified)
                              .Where(en => !en.IsRelationship ).ToList();

         foreach (var entry in pendingEntities)
         {
            EncryptEntity(entry.Entity);
         }

         int result = -999;
         try
         {
            result = base.SaveChanges();
         }
         catch
         {
             throw;
         }
         finally
         {
             foreach (var entry in pendingEntities)
             {
                 DecryptEntity(entry.Entity);
             }
         }

         return result;
     }
   }
}

7. Modificando nuestra clase Context:  

Por ultimo modificamos nuestra clase generada Context, haciendo que herede de nuestra clase CriptoDBContext.


// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace JhonPierre.CriptoEntities
{
using JhonPierre.Cripto.Entities;
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

public partial class CRIPTO_DATAEntities  :  CriptoDBContext
{
     public CRIPTO_DATAEntities()
     : base("name=CRIPTO_DATAEntities")
    {
    }

     protected override void OnModelCreating(DbModelBuilder modelBuilder)
     {
         throw new UnintentionalCodeFirstException();
     }

     public DbSet<Entidad> Entidad { get; set; }
  }

}

 

Listo! ahora solo nos queda por hacer el test con una aplicación de modo consola para casos de ejemplo.

4. Seleccionar las columnas a encriptar:   

Dentro de nuestro modelo de datos, debemos definir qué entidades y qué propiedades van a ser encriptadas y cuales no.

Para ello, vamos a desarrollar un atributo de propiedad, que nos ayudará a indicar si una propiedad, perteneciente a una entidad, será encriptada o no.

Entonces creamos nuestro atributo personalizado.

namespace JhonPierre.Cripto.Entities
{
  [System.AttributeUsage(System.AttributeTargets.Property)]
  public class Encrypted : System.Attribute
  {
   private bool _esEncriptada = true;
   public bool EsEncriptada
   {
    get { return _esEncriptada; }
    set { _esEncriptada = value; }
   }

   public Encrypted(bool esEncriptada)
   {
     _esEncriptada = esEncriptada;
   }
  }
}

5. Marcar las propiedades en nuestro modelo:   

Después de haber creado nuestro atributo de propiedad, debemos marcar las propiedades de nuestra entidad que van a ser encriptadas. Para ello modificamos nuestra clase entidad. Tener en cuenta que si usamos un archivo EDMX, tendremos que agregar esta modificación a nivel plantilla o template y no directamente en la clase.

Entonces nuestra clase entidad queda de la siguiente manera:


namespace JhonPierre.CriptoEntities
{
  using JhonPierre.Cripto.Entities;
  using System;
  using System.Collections.Generic;

  public partial class Entidad
  {
    [Encrypted(false)]
    public int IdEntidad { get; set; }

    [Encrypted(true)]
    public string Nombres { get; set; }

    [Encrypted(true)]
    public string Apellidos { get; set; }

    [Encrypted(true)]
    public string Telefono { get; set; }

    [Encrypted(false)]
    public string Comentario { get; set; }

    [Encrypted(true)]
    public string FechaNacimiento { get; set; }

    [Encrypted(false)]
    public string DatoNoEncriptado { get; set; }
  }
}

En el siguiente post, estaremos modificando nuestro objeto Context para poder encriptar y desencriptar las entidades haciendo uso de nuestras clases previamente desarrolladas.

 

1. Análisis del problema:

Cuando se manejan datos sensibles a través de nuestra aplicación, generalmente se opta por encriptar los datos en nuestra base de datos. Para ello nuestra aplicación debe tomar los datos de ingreso, encriptar los datos y registrarlos en una tabla. Lógicamente se debe hacer el proceso inverso para mostrar los datos sensibles al usuario de nuestra aplicación: leer los datos de la tabla, desencriptar los datos y mostrarlos al usuario.

Dentro del desarrollo de aplicaciones orientada a Dominio o Domain Driven Development (DDD), nosotros manejamos entidades de dominio, que son nuestros objetos que están mapeados siempre a uno o más registros en nuestra base de datos. A la herramienta que nos ayuda a realizar ese mapeo de objetos/registros, los conocemos como herramientas ORM (Object Relational Model).

Existen diversos frameworks que nos ayudan a implementar este mapeo de objetos/registros. Entre ellos Hibernate y EntityFramework son los más usados. Éste último está creciendo muy rápidamente y acercándose al gigante Hibernate.

Esta serie de posts tiene como objetivo brindar una solución a la encriptación de datos con Entity Framework sobre una base de datos SQL Server.

Manos a la obra..

2. Modelo de dominio:

Nuestro modelo de datos está compuesto por sólo una entidad, la cual contendrá datos encriptados y otros no encriptados. Es necesario saber que los campos encriptados deben almacenarse en un campo del tipo nvarchar(MAX), puesto que el valor encriptado puede contener más caracteres  que el valor original.

Modelo de datos a encriptar.

Modelo de datos a encriptar.

 

 

3. Clase de encriptación:

Lo siguiente es definir el algoritmo de encriptación que vamos a utilizar para cifrar nuestros datos.
En nuestro caso, vamos a usar el algoritmo estandar AES (AES).
Para tal caso, vamos a crear nuestra clase de ayuda que contenga los métodos de Encriptar y Desencriptar.

namespace JhonPierre.Cripto
{
   /// <summary>
   /// Administrador de la encriptación AES 128 bits.
   /// </summary>
   public class AESCryptoManager
   {
     // 128bit(16byte)IV and Key
     private string _aesIV = @"!QBD2WSL#EFC5RFV";
     private string _aesKey = @"6TGB&ZGN7ULK(IK<";

     public String AesIV
     {
      get { return _aesIV; }
      set { _aesIV = value; }
     }

     public String AesKey
     {
       get { return _aesKey; }
       set { _aesKey = value; }
     }

     /// <summary>
     /// Encripta el texto especificado como parámetro.
     /// </summary>
     /// <param name="text"></param>
     /// <returns></returns>
     public string Encriptar(object text)
     {
      if (text == null) return null;

      try
      {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
          aes.BlockSize = 128;
          aes.KeySize = 128;
          aes.IV = Encoding.UTF8.GetBytes(AesIV);
          aes.Key = Encoding.UTF8.GetBytes(AesKey);
          aes.Mode = CipherMode.CBC;
          aes.Padding = PaddingMode.PKCS7;

          // Convert string to byte array
          byte[] src = Encoding.Unicode.GetBytes(text.ToString());

          // encryption
          using (ICryptoTransform encrypt = aes.CreateEncryptor())
          {
            byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);

            // Convert byte array to Base64 strings
            return Convert.ToBase64String(dest);
          }
        }
      }
      catch
      {
        return null;
      }
    }

    /// <summary>
    /// Desencripta el texto cifrado enviado como parámetro.
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public string Desencriptar(string text)
    {
      if (text == null) return null;

      try
      {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
          aes.BlockSize = 128;
          aes.KeySize = 128;
          aes.IV = Encoding.UTF8.GetBytes(AesIV);
          aes.Key = Encoding.UTF8.GetBytes(AesKey);
          aes.Mode = CipherMode.CBC;
          aes.Padding = PaddingMode.PKCS7;

          // Convert Base64 strings to byte array
          byte[] src = System.Convert.FromBase64String(text);

          // decryption
          using (ICryptoTransform decrypt = aes.CreateDecryptor())
          {
            byte[] dest = decrypt.TransformFinalBlock(src, 0, src.Length);
            return Encoding.Unicode.GetString(dest);
          }
        }
      }
      catch
      {
       return null;
      }
    }

   }
}

 

5. CREANDO NUESTRA VISTA.

Nuestra vista será codigo HTML, mostrando un control Select por cada una de las partes de nuestra Fecha, en cada uno de estos controles selects usaremos nuestra directiva del tipo atributo e indicaremos la propiedad del modelo que vamos a enlazar, cuyo nombre es indicado en el atributo cp-date y además la parte de nuestra fecha que nuestra directiva se encargará de refrescar en la UI, esto es indicado en el atributo part.

<div>
	<select cp-date="myDate" part="month">
		<option disabled selected value="">MONTH</option>
		<option ng-repeat="m in months" value="{{m}}">{{m}}</option>
	</select>
	<select cp-date="myDate" part="day">
		<option disabled selected value="">DAY</option>
		<option ng-repeat="d in days" value="{{d}}">{{d}}</option>
	</select>
	<select cp-date="myDate" part="year">
		<option disabled selected value="">YEAR</option>
		<option ng-repeat="y in years" value="{{y}}">{{y}}</option>
	</select>
	<br/>
	<select cp-date="myDate" part="hour">
		<option disabled selected value="">HOUR</option>
		<option ng-repeat="m in hours" value="{{m}}">{{m}}</option>
	</select> _:_
	<select cp-date="myDate" part="minute">
		<option disabled selected value="">MINUTES</option>
		<option ng-repeat="d in minutes" value="{{d}}">{{d}}</option>
	</select>__
	<select cp-date="myDate" part="period">
		<option selected value="AM">AM</option>
		<option selected value="PM">PM</option>
	</select>
	<br/>
	Mi Modelo es: {{myDate}}
</div>

6. CREANDO NUESTRA DIRECTIVA.

Nuestra directiva, se encargará de observar la propiedad del modelo, cuyo nombre es indicado en el atributo cp-date y se encargará de refrescar en la UI la parte indicada en el atributo part.

jFaqDirectives.directive('cpDate', ['$log','$parse', function (log, parse) {
    return function(scope, elem, attrs){
		var _date = new Date();
		var _yearZero = 0;

		scope.$watch(attrs.cpDate, function(value) {
			if(value) {
				_date = new Date(value);
				log.info('date changed = ' + _date);
				updateUI();
			}
		});

		function populateYears() {
			var currentYear = new Date().getFullYear();
			_yearZero = currentYear;
			scope.years = [];
			for(var i= currentYear; i&gt; currentYear - 20 ; i--) {
				scope.years.push(i);
			}
		}

		function populateMonths() {
			scope.months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
		}

		function populateDays() {
			var daysInMonth = new Date(_date.getFullYear(), _date.getMonth() + 1, 0).getDate();
			scope.days = [];
			for(var i= 1; i &lt;= daysInMonth; i++) {
				scope.days.push(i);
			}
		}

		function populateHours() {
			scope.hours = [12,1,2,3,4,5,6,7,8,9,10,11];
		}

		function populateMinutes() {
			scope.minutes = [];
			for(var i= 0; i &lt;= 59; i++) {
				scope.minutes.push(i);
			}
		}

		function updateUI() {
			//TODO: usar Jquery
			if(attrs.part == 'month') {
				var m = _date.getMonth();
				//elem.val(scope.months[m]);
				elem.children()[m+1].selected = true;

			} else if(attrs.part == 'day') {
				var m = _date.getDate();
				//elem.val(m);
				elem.children()[m].selected = true;

			} else if(attrs.part == 'year') {
				var y = _date.getFullYear();
				var index = _yearZero - y + 1;
				//elem.val(m);
				if(index &gt;= 0) {
					elem.children()[index].selected = true;
				}

			} else if(attrs.part == 'hour') {
				var m = _date.getHours();
				log.info('hours ' + m);
				if(m &gt; 11) m = m - 12;
				elem.children()[m + 1].selected = true;

			} else if(attrs.part == 'minute') {
				var m = _date.getMinutes();
				elem.children()[m + 1].selected = true;

			} else if(attrs.part == 'period') {
				var m = _date.getHours();
				if(m &gt; 11) {
					elem.children()[1].selected = true;
				}else {
					elem.children()[0].selected = true;
				}
			}
		}

		function getMonthIndex(name) {
			for(var i=0; i&lt; scope.months.length; i++) {
				if(scope.months[i] == name) {
					return i;
				}
			}
			return 0;
		}

		function bindEvents() {
			//TODO: usar Jquery
			if(attrs.part == 'month') {
				elem.bind('change', function() {
					_date.setMonth( getMonthIndex(this.value) );
					log.info(this.value);
					refreshModel();
				});

			} else if(attrs.part == 'day') {
				elem.bind('change', function() {
					_date.setDate(this.value);
					log.info(this.value);
					refreshModel();
				});
			} else if(attrs.part == 'year') {
				elem.bind('change', function() {
					_date.setYear(this.value);
					log.info(this.value);
					refreshModel();
				});
			}  else if(attrs.part == 'hour') {
				elem.bind('change', function() {
					_date.setHours(this.value);
					log.info(this.value);
					refreshModel();
				});

			} else if(attrs.part == 'minute') {
				elem.bind('change', function() {
					_date.setMinutes(this.value);
					log.info(this.value);
					refreshModel();
				});

			} else if(attrs.part == 'period') {
				elem.bind('change', function() {
					if(this.value == 'AM') {
						if(_date.getHours() &gt;= 12) {
							_date.setHours(_date.getHours() - 12);
						}
					}else {
						var h = _date.getHours();
						if(h != 12) {
							_date.setHours(h + 12);
						}
					}
					log.info(this.value);
					refreshModel();
				});
			}

		}

		function initialize() {
			populateYears();
			populateMonths();
			populateDays();
			populateHours();
			populateMinutes();
			bindEvents();
		}

		function refreshModel() {

			var newValue = _date;
			log.info('refreshModel to ' + _date);
                        //Esto es para que nuestro scope se entere del cambio.
			var parsed = parse(attrs.cpDate);
			scope.$apply(function() {
				parsed.assign(scope, newValue);
			});
		}

		initialize();
    };
}]);

7. PROBANDO NUESTRA DIRECTIVA.

Ahora si, lanzamos nuestro página en localhost y accedemos a nuestra página /date. La aplicación nos debe mostrar la fecha inicial colocada en el método initialize de nuestro controlador. Y podemos interactuar con los controles select, vemos que al cambiar cualquier item, nuestro modelo es actualizado y mostrado debajo de los controles select.

date

Hasta la próxima!!

1. CUAL ES LA IDEA?.

El objetivo de este post es crear un control html que será enlazado a una propiedad del tipo Fecha y Hora de nuestro modelo. Si bien es cierto que hay muchos controles ya implementados en el mercado, el objetivo de este post es implementar un control simple, extendiendo nuestro lenguaje HTML a través de la implementación de  Directivas en angularJs… bien, como siempre manos a la obra.

2. EXTENDIENDO NUESTRO HTML.

Definiremos nuestra solución como un par de atributos más del control HTML Select <Select>, el primer atributo nos dirá cuál es la propiedad del tipo Date dentro de nuestro modelo, mientras que el segundo atributo nos dirá que parte de la Fecha queremos controlar. Las partes que contemplaremos en esta solución son las siguientes: month, day, year, hour, minute y period.

La implementación, como mencionamos anteriormente, la haremos construyendo una directiva del tipo atributo en angularJs.

3. AGREGANDO EL CONTROLADOR A NUESTRO ROUTER.

Como siempre empezamos direccionando nuestro request a una vista y controlador particular, dentro de nuestro archivo app.js.

var miModulo = angular.module('miModulo',['jFaq.services','jFaq.directives'])
.config(['$routeProvider', function ($routeProvider) {
	$routeProvider
		.when('/date', {controller: 'dateController', templateUrl: 'views/fecha.html'})
		.otherwise({redirectTo:'/'});
}]);

4. CREANDO NUESTRO CONTROLADOR.

Nuestro controlador contendra nuestro modelo, el cuál consta de una variable tipo Date. Además definiremos una función de inicialización de nuestro modelo, la cuál se encargará de cambiar nuestro modelo a una fecha determinada. En este controlador tambien definiremos un conjunto de arrays necesarios por la directiva.

var dateController = miModulo.controller('dateController', function($scope, $window, $log){

	$scope.myDate = new Date();
	//propiedades usadas por la directiva.
	$scope.months = [];
	$scope.years = [];
	$scope.days = [];
	$scope.hours = [];
	$scope.minutes = [];
	//
	$scope.initalize = function() {
		$scope.myDate = new Date(2012, 0, 22);
		$scope.myDate.setHours(6);
		$scope.myDate.setMinutes(30);
		$log.info('date initialized = ' + $scope.myDate);
	};

	$scope.initalize();
});

9. Creando nuestro simple protocolo de mensajes.

Antes de empezar a codear la parte cliente de la conexion, necesitamos definir la estructura de nuestro mensaje. Para nuestro caso particular, necesitamos basicamente dos cosa: el nombre de usuario y el texto que envia. Para este caso particular, la forma mas sencilla de implementar esta estructura es la siguiente:

Mensaje = [ nombre_usuario + delimitador + texto_mensaje ]

De esta forma podemos obtener quien envio el mensaje y cual es el mensaje que envio. Nuestro delimitador debera ser una secuencia especial de caracteres de modo que no podamos confundir el nombre del usuario con el texto del mensaje.

Ahora si, estamos listos para implementar nuestro cliente.

10. Creando el socket cliente.

Como vimos anteriormente en nuestra vista chatroom.jade estamos incluyendo el archivo javascript client.js, este archivo contendra el codigo necesario para crear nuestro objeto socket cliente, luego usar este objeto para abrir una conexion al server que nos permitira enviar y recibir mensajes. El archivo contiene el siguiente codigo:

client.js

var username = '';
var iosocket ;
var delimitador = &quot;&lt;.&gt;&quot;;

var hola = function() {
  alert(username);
};

var enviarMsj = function() {
  var text = $('#txtMensaje').val();
  showMensaje(username, text);
  enviar2Server(text);
  $('#txtMensaje').val('');
};

var showMensaje = function(name, text) {
  var div = document.createElement('blockquote');
  div.className = 'example-right';
  var p = document.createElement('p');
  var node = document.createTextNode(text);
  p.appendChild(node);

  //var username = 'Jhon';
  var pname = document.createElement('p');
  pname.className = 'name';
  var nodename = document.createTextNode(name + ':');
  pname.appendChild(nodename);

  div.appendChild(pname);
  div.appendChild(p);

  var innerScroll = $('#innerScroll');
  innerScroll.append(div);

  innerScroll.scrollTop(9999999);
};

$(function(){
  username = $('#txtUserName').val();
  iosocket = io.connect();

  iosocket.on('connect', function () {
      $('#estado').html('&lt;li&gt;Conectado&lt;/li&gt;');

      iosocket.on('message', function(message) {
	 var array = message.split(delimitador);
	 var user = array[0];
	 var text = array[1];
	 showMensaje(user, text);
      });

      iosocket.on('disconnect', function() {
	  $('#estado').html('&lt;li&gt;Disconnected&lt;/li&gt;');
      });
  });

  $('#txtMensaje').keypress(function(event) {
      if(event.which == 13) {
	  event.preventDefault();
	  enviarMsj();
      }
  });

  //hola();
});

var enviar2Server = function (text) {
  var mensaje = username + delimitador + text;
  iosocket.send(mensaje);
};

Como podemos observar, hacemos uso de dos modulos particulares: el primer modulo es el socket client que nos brinda acceso a los objetos Socket, el cual se ubica en el archivo socket.io/scoket.io.js, que es creado por el modulo Socket.io de nodeJs. El segundo es jQuery que nos sirve para acceder a la UI y generar los delegados de eventos del DOM.

11. Enjoy!

Ejecutamos la aplicacion, para ello vamos a una consola y escribimos el siguiente comando dentro de nuestro directorio principal en donde se encuentra ubicado nuestro codigo:

$ node app.js

y veremos algo como:

   info  - socket.io started
Express server listening on port 3000

Ahora si ya podemos testear la aplicacion, vamos a nuestro explorador preferido (Evita usar Internet Explorer), y vamos a http://localhost:3000/ , ingresamos nuestro nombre de usuario e ingresamos a la sala de chat. Puedes abrir un segundo explorador y entrar con un usuario diferente para que puedas probar el chat.

Lo interesante es que a la vez hemos hecho una aplicacion que aplica el concepto de responsive application, es decir si accedes a la aplicacion desde el navegador en un smartphone, veras que funciona igual!.

Espero se hayan divertido una vez mas, con nodeJs puedes hacer cosas muy interesantes, asi que solo queda practicar un poco mas.

Les dejo la aplicacion desplegada aqui: http://quiet-plateau-4229.herokuapp.com/

7. Creando nuestra hoja de estilos.

Nuestra hoja de estilos, ubicado dentro de nuestro directorio pubic/stylesheets/ luce de la siguiente manera:

main.css

body {
  font: 1em/1.4 Cambria,Georgia,sans-serif;
}

.outer {
  width: 100%;
}

.content {
  border-style: solid;
  border-width: 1px;
  border-color:blue;
  background-color: cyan;
  text-align: center;
  width: 50%;
  margin: 0 auto;
}

.texto {
  display:block;
  font-color: green;
  font-weight: bold;
}

.boton {
  weight: 200px;
  display: block;
  background-color: #FF9900;
  border-style: solid;
  border-width: 1px;
  border-color: #FF0000;
  margin: 0 auto;
}

.example-right {
  position:relative;
  padding:1px 10px;
  margin:5;
  color:#fff;
  background:#5a8f00; /* default background for browsers without gradient support */
  /* css3 */
  background:-webkit-gradient(linear, 0 0, 0 100%, from(#b8db29), to(#5a8f00));
  background:-moz-linear-gradient(#b8db29, #5a8f00);
  background:-o-linear-gradient(#b8db29, #5a8f00);
  background:linear-gradient(#b8db29, #5a8f00);
  -webkit-border-radius:10px;
  -moz-border-radius:10px;
  border-radius:10px;
  margin-right:40px;
}

/* display of quote author (alternatively use a class on the element following the blockquote) * /
.example-right + p {margin:15px 0 2em 85px; font-style:italic;}  */

.example-right:after {
  content:"";
  position:absolute;
  bottom:-20px; /* value = - border-top-width - border-bottom-width */
  left:50px; /* controls horizontal position */
  border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
  border-style:solid;
  border-color:#5a8f00 transparent;
  /* reduce the damage in FF3.0 */
  display:block;
  width:0;
   top:16px;
	right:-40px; /* value = - border-left-width - border-right-width */
	bottom:auto;
    left:auto;
	border-width:15px 0 0 40px; /* vary these values to change the angle of the vertex */
	border-color:transparent #5a8f00 ;
}

/* creates the triangle */
.NOexample-right:after {
	content:"";
	position:absolute;
	bottom:-50px;
	left:50px;
	border-width:0 20px 50px 0px;
	border-style:solid;
	border-color:transparent #5a8f00;
    /* reduce the damage in FF3.0 */
    display:block;
    width:0;

}

blockquote {
    margin:1em 0;
}

blockquote p {
    margin-top:1;
    margin-left:5;
    font-size:14px;
    word-wrap: break-word;
}

blockquote p.name{
    margin:2;
    font-size:11px;
    word-wrap: break-word;
    font-weight:bold;
    color:blue;
}

.container {
  width: 400px;
  height:600px;
  /*border-style:solid;
  border-color:blue;
  border-width:1px;*/
  margin: 0 auto;
}

.innerScroll {
  width: 400px;
  height:570px;
  border-style:solid;
  border-color:black;
  border-width:1px;
  overflow-y:auto;
  overflow-x:hidden;
}

#txtMensaje {
  max-height:25px;
  width:300px;
}

#btnEnviar {
  width: 95px;
  height: 25px;

}

8. Modificando nuestro archivo app.js.

Nuestro archivo principal, en donde nos encargamos de crear nuestro servidor y nuestro socket, es el archivo app.js, en donde estaremos incluyendo los mòdulos necesarios e implementando el envìo y recepciòn de mensajes.

app.js


/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path')
  , io = require('socket.io');

var delimitador = "<.>";

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
  app.use(require('stylus').middleware(__dirname + '/public'));
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);
app.get('/about', function(req, res){
  res.render('about', { title:'Acerca De' });
});

app.post('/chatroom', function(req, res){
  var name = req.param('userName', null);  // second parameter is default
  //console.log('userName = ' + name);
  res.render('chatroom', { title:'chatroom', username: name });
});

var myApp = http.createServer(app);

myApp.listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

var onConnection = function (socket) {
    socket.on('message', function(msg) {
      onMessage(msg, socket);
    });
};

var onMessage = function (msg, socket) {
    //console.log('Message Received: ', msg);
    socket.broadcast.emit('message', msg);
    //console.log('Message Emitted: ', msg);
};

var sio = io.listen(myApp);
sio.on('connection', onConnection);

5. Creando nuestra aplicación.

Una vez instalados node.js, npm y express.js, lo siguiente es crear nuestra aplicación. Para ello ejecutamos en una consola el siguiente comando:

$ express -c stylus expresschat

El uso del atributo -c indica a express que nosotros queremos utilizar stylus como renderizador de nuestro css. Mientras que expresschat es el nombre de nuestra aplicación.

Luego ejecutamos el sguiente comando:

$ cd expresschat && npm install

Con esto ya temenos creada nuestra estructura, por defecto express creará dos o tres vistas, llamadas index.jade y users.jade.

Nuestra aplicación hará uso de broadcasting basado en web sockets, para poder acceder a estos web sockets, existe una herramienta implementada en node.js llamada Socket.io, la cual debemos agregar como dependencia a nuestra aplicación. Para hacer esto ejecutamos el siguiente comando.

$ npm install socket.io

Bien ahora si nos damos cuenta, Express creo un archivo llamado package.json, en nuestro directorio raíz. Antes de empezar tenemos que editarlo en orden de agregar nuestra dependencia a socket.io. El archivo final debe lucir como se muestra a continuación.

{ "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
     "start": "node app.js"
   },
  "dependencies": {
     "express": "3.2.4",
     "socket.io": "~0.9.6",
     "jade": "*",
     "stylus": "*"
   },
   "engines": {
     "node": "0.8.x",
     "npm": "1.2.x"
   }
}

6. Creando nuestras vistas.

Express.js es agnóstico al tipo de lenguaje que uses al momento de crear un template, para este caso usaremos el motor de plantillas Jade, te darás cuenta luego que es sencillo de usar.

A parte de las vistas generadas por defecto por Express, ubicadas en la carpeta views. Crearemos dos vistas principales, que se muestran a continuación:

chatroom.jade

doctype 5
html
  head
    title= title
    link(rel="stylesheet", href="/stylesheets/main.css")
    script(src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")
    script(src="/socket.io/socket.io.js")
    script(src="/javascripts/client.js")
  body
   input(id="txtUserName", type="hidden", value=username)
   div(class="container")
    div(id="estado")
    div(id="innerScroll", class="innerScroll")
    input(id="txtMensaje", type="text", value="", placeholder="mensaje")
    input(id="btnEnviar", type="button", value="Enviar", onclick="enviarMsj();")
   

index.jade

extends layout

block content
  div(class="outer")
   div(class="content")
    form(action="/chatroom", method="POST", name="user")
     span(class="texto") Ingresa tu nick:
     input(type="text", placeholder="nick name", name="userName", value="", class="")
     input(type="submit", value="Empezar Chat", class="boton")

Además modificaremos nuestro archivo base denominado

layout.jade

doctype 5
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/main.css')
  body
    block content

1. Creando una sala de chat.

Lo que se describe a continuación es la implementación de una sala de chat pública, simple y sencilla, en la que un usuario puede ingresar su nombre y empezar a chatear con otros usuarios. Debido a efectos de tiempo, no se realizarán validaciones sobre los mensajes, de modo que cualquiera podría romper la aplicación. La idea de este tutorial es poder conocer node.js y algo de la tecnología conocida como WebSockects implementada en node.js con el módulo Socket.io.

Asi que manos a la obra.

2. Qué es node.js?

Node.js es una plataforma desarrollada en javascript bajo el entorno de ejecución de Chrome, que es usada del lado del servidor, para la creación rápida de sistemas web escalables. Esta plataforma usa 100% javascript y se basa en una arquitectura basada en eventos de entrada y salida asíncrona y  no bloqueante. Con node.js podras crear servidores http, servidores ftp o servidores tcp, sin el uso de otras herramientas como Apache. Si te gusta javascript, esto es para tí.

3. Instalando node.js en Ubuntu?

Para instalar node.js en Ubuntu, hay muchos tutoriales que pueden seguir, e incluso para obtener y compilar la última versión de node.js en sus sistemas.

Aqui les dejo los pasos obtenidos de esta página. (Es mejor instalarlos como root para poder tenerlos de manera global dentro de linux)

$ sudo apt-get update
$ sudo apt-get install git-core curl build-essential
$ sudo apt-get install openssl libssl-dev
$ git clone https://github.com/joyent/node.git && cd node
$ ./configure
$ make
$ sudo make install
$ echo 'export NODE_PATH=/path_to/node' >> ~/.bashrc
$ echo 'export PATH=$PATH:/path_to/node' >> ~/.bashrc
$ source ~/.bashrc
$ node -v

4. Instalando Express.js en Ubuntu?

Express.js es un light-weight application framework que nos facilita la creación de aplicaciones web bajo la arquitectura MVC. Con este framework tu puedes manejar de manera fácil los enrutadores, además puedes usar distintos lenguajes para generar plantillas como ejs, jade, etc.

Para la instalación de Express, una vez instalado node.js, basta con ejecutar el siguiente comando.

$ npm install -g express