Páginas

miércoles, 23 de marzo de 2016

Creación de mi primera app con Ionic

Introducción

Vamos a ver los pasos necesarios para crear unas aplicaciones de ejemplo con Ionic en base a las plantillas que se ofrece.

1) Creamos una carpeta 'apps-ionic' y nos movemos a ella.

2) Lanzamos el siguiente comando para crear un app llamado ejemplo1:

   ionic start -a "ejemplo1" -i es.ine.sgtic.ejemplo1 ejemplo1 blank

    -a ejemplo1              -> Nombre de la aplicación
    -i es.ine.sgtic.ejemplo1 -> Nombre del dominio/id de la aplicación
    ejemplo1                 -> Nombre del directorio donde generará el proyecto
    blank                    -> Nombre de la plantilla que hemos usado

   Nota. En mi caso durante la creación del proyecto base ha saltado el error:

     Error Initializing app: Error: connect ETIMEDOUT 192.30.252.131:443

Googleando el problema he visto que hay que definir una variable PROXY a nivel de sistema dado que Ionic no usa la config del proxy definida en NPM. Por tanto creamos una variable de entorno llamada PROXY y le metemos el valor http://usuario:password@xxx.xxx.xxx.xxx:pppp

Tras el ajuste he tenido que reabrir la ventana de comandos para que pille el cambio y ejecute el comando con normalidad. La traza obtenida ha sido:

C:\Users\IEUser\Desktop\apps-ionic>ionic start -a "ejemplo1" -i es.ine.sgtic.ejemplo1 ejemplo1 blank
Creating Ionic app in folder C:\Users\IEUser\Desktop\apps-ionic\ejemplo1 based on blank project
    Downloading: https://github.com/driftyco/ionic-app-base/archive/master.zip
    [=============================]  100%  0.0s
    Downloading: https://github.com/driftyco/ionic-starter-blank/archive/master.zip
    [=============================]  100%  0.0s
    Updated the hooks directory to have execute permissions
    Update Config.xml
    Initializing cordova project

    Your Ionic project is ready to go! Some quick tips:

     * cd into your project: $ cd ejemplo1

     * Setup this project to use Sass: ionic setup sass

     * Develop in the browser with live reload: ionic serve

     * Add a platform (ios or Android): ionic platform add ios [android]
       Note: iOS development requires OS X currently
       See the Android Platform Guide for full Android installation instructions:
       https://cordova.apache.org/docs/en/edge/guide_platforms_android_index.md.html

     * Build your app: ionic build <PLATFORM>

     * Simulate your app: ionic emulate <PLATFORM>

     * Run your app on a device: ionic run <PLATFORM>

     * Package an app using Ionic package service: ionic package <MODE> <PLATFORM>

    For more help use ionic --help or ionic docs

    Visit the Ionic docs: http://ionicframework.com/docs


    New! Add push notifications to your Ionic app with Ionic Push (alpha)!
    https://apps.ionic.io/signup
    +---------------------------------------------------------+
    + New Ionic Updates for March 2016
    +
    + The View App just landed. Preview your apps on any device
    + http://view.ionic.io
    +
    + Invite anyone to preview and test your app
    + ionic share EMAIL
    +
    + Generate splash screens and icons with ionic resource
    + http://ionicframework.com/blog/automating-icons-and-splash-screens/
    +
    +---------------------------------------------------------+


3) Ya tendríamos montado el proyecto con su esqueleto, para ver funcionando el app deberemos seguir los pasos que nos indica la propia traza de Ionic:

   cd ejemplo1         --> Nos movemos a la carpeta de la app
   ionic setup sass  --> Ajustamos hojas de estilo
   ionic serve           --> Arranca un server http por el puerto 8100 para pruebas

4) Ahora podemos repetir los pasos anteriores con otras plantillas de ejemplo, hay algunas mas. Para sacar la lista completa de plantillas de ionic deberemos ejecutar el comando

   ionic start --list
 
Nota. No debemos olvidar colocarnos en el directorio padre 'apps-ionic' para tener los distintos ejemplos
organizados en una misma carpeta:

   ionic start -a "ejemplo2" -i es.ine.sgtic.ejemplo2 ejemplo2 tabs
   ionic start -a "ejemplo3" -i es.ine.sgtic.ejemplo3 ejemplo3 sidemenu
   ionic start -a "ejemplo4" -i es.ine.sgtic.ejemplo4 ejemplo4 complex-list
   ionic start -a "ejemplo5" -i es.ine.sgtic.ejemplo5 ejemplo5 maps
   ionic start -a "ejemplo6" -i es.ine.sgtic.ejemplo6 ejemplo6 salesforce


Nota. En el ejemplo4 (complex-list) se ve lo mismo que en el ejemplo1 ¿?

En la siguiente entrada nos dedicaremos a 'destripar' el contenido de la estructura de carpetas generada para cada proyecto, veremos cómo visualizar la aplicación directamente en el emulador de un dispositivo con Android y empaquetaremos el app para instalarlo en un app real.

Un saludo

Instalación del entorno de desarrollo Ionic

Introducción

Vamos a resumir los pasos necesarios para instalar el entorno de desarrollo de aplicaciones híbridas basadas en Ionic en una virtualización con Windows-7 e IExplorer 11.

Instalaremos las herramientas: Node.js, Git, Bower, Gulp, Cordova, Ioni, JDK y Android Studio.

Proceso de instalación. Parte I

1) Instalamos node.js bajando el instalador de la página web oficial (https://nodejs.org/en/) y ejecutándolo. La página detecta el sistema operativo que tengamos instalado y nos ofrecerá el instalador adecuado.

2) Al instalar Node.js se nos habrá descargado también el gestor de paquetes NPM de Node.js. Verificamos que ambos componentes están correctamente instalados lanzando los comandos:

   node -v   --> v4.4.0
   npm -v    --> 2.14.20

Nota. Si usamos NPM detrás de un proxy deberemos configurarlo mediante el comando:

   npm config set proxy http://usuario@password@xxx.xxx.xxx.xxx:pppp

Con este comando se nos creará el fichero .npmrc en el home del usuario con la config del proxy

3) Seguidamente instalamos GIT (un sistema de control de versiones) que vamos a necesitar después para Bower y para Ionic-cli. Instalamos GIT bajando el instalador de la página oficial (https://git-scm.com/downloads) y ejecutándolo. La página detecta el sistema operativo que tengamos instalado y nos ofrecerá el instalador adecuado.

4) Añadimos la ruta de instalación de la carpeta 'c:\Program Files\Git\cmd' (carpeta donde se ha instalado por defecto GIT) a la variabla de entorno PATH de nuestro sistema.

5) Verificamos que el componente está correctamente instalado lanzando el comando:

   git --version  --> git version 2.7.4.windows.1

6) Seguidamente instalaremos Bower, que es la herramienta que usaremos como administrador de las dependencias de librerías de nuestra aplicación. Para instalarlo usaremos NPM, el comando será:

   npm install bower -g

Nota. La opción 'g' indica al instalador que queremos una instalación global para poder acceder a bower desde cualquier directorio.

7) Verificamos que el componente está correctamente instalado lanzando el comando:

   bower -v   --> 1.7.7

8) Lo siguiente es instalar GULP, un sistema de construcción que automatiza tareas habituales del entorno de desarrollo: minificación de código JavaScript, compresión de imágenes, validación de sintaxis, etc. Para instalarlo usaremos NPM, el comando será:

   npm install gulp -g

9) Verificamos que el componente está correctamente instalado lanzando el comando:

   gulp -v   --> [06:31:50] CLI version 3.9.1

10) Por último instalamos Ionic y Apache Cordova desde el NPM. En este caso vamos a ser estrictos con la versión, para tener definidos las versiones que nos interesan. Para instalarlos usaremos NPM y los comandos serán:

   npm install cordova -g
   npm install ionic -g

Nota. Si necesitamos una versión concreta de cualquiera de los componentes que instalamos con npm podemos usar comandos de tipo:

     npm install cordova@5.0.0 -g

Durante la instalación se mostrarán warnings en caso de conflictos con las versiones.

11) Verificamos que ambos componentes están correctamente instalados lanzando los comandos:

   cordova -v       --> 6.0.0
   ioniccordova -v  --> 1.7.14

Con esto ya tendríamos instalado todo lo necesario para desarrollar aplicaciones con Ionic.

Las aplicaciones construidas se podrían probar directamente sobre el movil o la tablet elegida, pero si queremos trabajar con varios SSOO de varias versiones, siempre es mas cómodo instalar en el PC emuladores de dichas plataformas: Android Studio, Visual Studio, etc, que nos permitirán probar nuestra app en emulaciones de diferentes dispositivos físicos y/o con distintas versiones del SO objetivo (Android, IOs, Windows Phone, etc).

Proceso de instalación. Parte II

Para poder probar en distintos emuladores de dispositivos con diferentes versiones de los sistemas operativos necesitaremos instalar el JDK y un  emulador de la plataforma, en nuestro caso vamos a realizar las pruebas con Android, por lo que instalaremos el SDK de Android (Android Studio) y el JDK de Java.

12) Descargamos el JDK (ojo no del JRE) desde la URL (http://www.oracle.com/technetwork/es/java/javase/downloads/index.html). Como en nuestro caso estamos trabajando en una imagen de Windows 7 de 32 bits, bajaremos el fichero 'jdk-8u73-windows-i586.exe'. Lanzaremos el instalador y punto.

13) Descargamos Android Studio desde la URL (http://developer.android.com/sdk/index.html). Tras la descarga lanzaremos el instalador 'android-studio-bundle-141.2456560-windows' y seguiremos el asistente definiendo la instalación por defecto.

Un Saludo

martes, 22 de marzo de 2016

Servicio web RESTful

Introducción a REST

REST (Representational State Transfer) es un tipo de arquitectura software en el que las interfaces entre sistemas usan directamente HTTP para obtener datos o indicar la ejecución de operaciones sobre los datos, en cualquier formato (XML, JSON, etc) sin las abstracciones adicionales de los protocolos basados en patrones de intercambio de mensajes, como por ejemplo SOAP.

Los sistemas que siguen los principios REST se llaman con frecuencia RESTful.

Para poder crear/consumir servicios web REST, tenemos el API JAX-RS (Java API for RESTful Web Services) en el JRE/JDK de java

Algunos conceptor básicos

1) Programar un servicio web RESTfull debemos cambiar el enfoque, los conceptos básicos de los servicios web tradicionales (RPC) basados en la invocación de métodos cambian completamente.Ya no invocamos métodos del tipo addEmpleado(emp), removeEmpleado(emp), updateEmpleado(emp), deleteEmpleado(emp),  ahora el 'recurso' empleado se expone en una URL (http://localhost:8080/rest-ws/rest/empleado) sobre la quepodemos realizar operaciones básicas de tipo create, read, update y delete invocando la URL mediante los métodos:POST, GET, PUT, DELETE de HTTP.

Algunos ejemplo:
  • Para dar de alta un empleado llamaríamos la siguiente URL por POST: 
http://localhost:8080/rest-ws/rest/empleado/33/juan/español/español
  • Para dar de baja un empleado llamaríamos la siguiente URL por DELETE:
http://localhost:8080/rest-ws/rest/empleado/1
  • Para recuperar un empleado llamaríamos la siguiente URL por GET:
http://localhost:8080/rest-ws/rest/empleado/1
  • Para actualizar un empleado llamaríamos la siguiente URL por PUT:
http://localhost:8080/rest-ws/rest/empleado/33/Juan/Español/Español
  • Para obtener la lista de empleados llamaríamos la siguiente URL por GET:
http://localhost:8080/rest-ws/rest/empleado/list
  • Para obtener el número de empleados llamaríamos la siguiente URL por GET:
http://localhost:8080/rest-ws/rest/empleado/count

2) Destacar que la misma URI puede tener varias finalidades, en los ejemplos anteriores usamos la misma URI para el alta de un empleado y para su actualización, sólo se diferencian en el método HTTP por el que se invoca y en este ejemplo en los parámetros
  
3) Destacar también que los parámetros se añaden en la URI del recurso, es en servicio web quien tiene mapeados cada uno de ellos
  
4) Destacar que se trabaja principalmente cadenas de texto, en envío de cualquier binario deberá ser codificado
  
5) En RESTfull existe el wadl, que es equivalente a los wsdl de los servicios web basados en XML.

7) Generar/Encontrar el wadl

Si usamos el framework Jersey, la implementación de referencia de JAX-RS, como en el presente ejemplo, podremos acceder al fichero wadl con la URL: http://localhost:8080/rest-ws/rest/application.wadl

8) Generar cliente Java desde wadl

Hay una herramienta llamada WALD que utiliza Jersey 1.x and JAX-RS 2.0 para generar un cliente Java de un determinado fichero *.wadl. Hay un plugin disponible para Maven.


Servicio web ejemplo


Fichero web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <servlet>
    <servlet-name>jersey-serlvet</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>es.ine.sgtic.rest</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
      <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey-serlvet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>


Fichero EmpleadoRest.java


package es.ine.sgtic.rest;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import es.ine.sgtic.model.pojo.Empleado;
import es.ine.sgtic.model.pojo.Empleados;
import es.ine.sgtic.servicios.EmpleadoManager;

@Path("/empleado")
public class EmpleadoRest {
    
    private EmpleadoManager empMngr = new EmpleadoManager();
    
    @GET
    @Path("{id: [0-9]+}")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/{id}
     */
    public Empleado readEmpleado(@PathParam("id")String id) {
        
        return empMngr.readEmpleado(id);
    }
    
    @POST
    @Path("{id: [0-9]+}/{nombre}/{ape1}/{ape2}/{email}")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/{id}/{nombre}/{ape1}/{ape2}/{email}
     */
    public Empleado createEmpleado(@PathParam("id")String id, 
                                   @PathParam("nombre")String nombre, 
                                   @PathParam("ape1")String ape1, 
                                   @PathParam("ape2")String ape2,
                                   @PathParam("email")String email) {
        
        return empMngr.createEmpleado(id, nombre, ape1, ape2, email);
    }
    
    @PUT
    @Path("{id: [0-9]+}/{nombre}/{ape1}/{ape2}/{email}")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/{id}/{nombre}/{ape1}/{ape2}/{email}
     */
    public Empleado updateEmpleado(@PathParam("id")String id, 
                                   @PathParam("nombre")String nombre, 
                                   @PathParam("ape1")String ape1, 
                                   @PathParam("ape2")String ape2,
                                   @PathParam("email")String email) {
        
        return empMngr.updateEmpleado(id, nombre, ape1, ape2, email);
    }
    @DELETE
    @Path("{id: [0-9]+}")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/{id}
     */
    public Empleado deleteEmpleado(@PathParam("id")String id) {
        
        return empMngr.deleteEmpleado(id);
    }
    
    @GET
    @Path("/count")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/count
     */
    public String getCount() {
        
        return empMngr.getNumeroEmpleados();
    }
    
    @GET
    @Path("/list")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * http://localhost:8080/rest-ws/rest/empleado/list
     */
    public Empleados getEmpleados() {

        return empMngr.getListaEmpleados();
    }
    
    @GET
    @Path("/find")
    @Produces(MediaType.APPLICATION_JSON)
    /*
     * Debería responder a cualquiera de estas combinaciones
     * http://localhost:8080/rest-ws/rest/empleado/find?id=2
     * http://localhost:8080/rest-ws/rest/empleado/find?nombre=pepe
     * http://localhost:8080/rest-ws/rest/empleado/find?nombre=pepe&ape1=garcia
     */
    public Empleados findEmpleado(@QueryParam("id")String id, 
                                     @QueryParam("nombre")String nombre, 
                                  @QueryParam("ape1")String ape1, 
                                  @QueryParam("ape2")String ape2,
                                  @QueryParam("email")String email) {
        
        return empMngr.getListaEmpleadosCoincidentes(id, nombre, ape1, ape2, email);
    }
}


Fichero EmpleadoManager.java


package es.ine.sgtic.servicios;

import es.ine.sgtic.model.dao.EmpleadoDao;
import es.ine.sgtic.model.pojo.Empleado;
import es.ine.sgtic.model.pojo.Empleados;

public class EmpleadoManager {

    private static final EmpleadoDao empDao = EmpleadoDao.getInstance();

    public Empleado createEmpleado(final String id, final String nombre, final String ape1, final String ape2, final String email){

        final Empleado emp = new Empleado();
        emp.setId(id);
        emp.setNombre(nombre);
        emp.setApe1(ape1);
        emp.setApe2(ape2);
        emp.setEmail(email);

        return empDao.createEmpleado(id, emp);
    }

    public Empleado readEmpleado(String id){

        return empDao.readEmpleado(id);
    }

    public Empleado updateEmpleado(final String id, final String nombre, final String ape1, final String ape2, final String email){

        final Empleado emp = new Empleado();
        emp.setId(id);
        emp.setNombre(nombre);
        emp.setApe1(ape1);
        emp.setApe2(ape2);
        emp.setEmail(email);

        return empDao.updateEmpleado(id, emp);
    }

    public Empleado deleteEmpleado(String id){

        return empDao.deleteEmpleado(id);
    }

    /**
     * Ojo aquí el truco está en devolver el objeto 'empleados' que contiene 
       internamente la lista de empleados, de otro modo estaríamos devolviendo
       una lista con N objetos empleado pero sin un 'nodo raiz', dos horas de 
       pelea para enterder el error que daba.
     * @return
     */
    public Empleados getListaEmpleados(){

        final Empleados empleados = new Empleados();
        empleados.getLista().addAll(empDao.getListaEmpleados().values());
        return empleados;
    }

    public Empleados getListaEmpleadosCoincidentes(final String id, final String nombre, final String ape1, final String ape2, final String email){

        final Empleados empleados = new Empleados();
        empleados.getLista().addAll(empDao.getListaEmpleadosCoincidentes(id, nombre, ape1, ape2, email).values());
        return empleados;
    }
    
    public String getNumeroEmpleados(){

        return empDao.getNumeroEmpleados();
    }
}


Fichero EmpleadoDao.java


package es.ine.sgtic.model.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;

import es.ine.sgtic.model.pojo.Empleado;

@XmlRootElement
/**
 * Clase de acceso a datos, la montamos para evitar tener que acceder a 
 * una BBDD de verdad. Al ser un Singleton hay una única instancia de la 
 * clase y tendremos persistencia entre llamada y llamada de la lista de
 * empleados.
 */
public class EmpleadoDao {

    private static EmpleadoDao instance = null;
    final Map<String, Empleado> empleados;

    public static EmpleadoDao getInstance() {
        if(instance == null) {
            instance = new EmpleadoDao();
        }
        return instance;
    }
    
    /**
     * Constructor donde metemos algunos empleados de prueba
     */
    protected EmpleadoDao(){
        
        empleados = new HashMap<String, Empleado>();
        Empleado emp = new Empleado();
        emp.setId("1");
        emp.setNombre("nombre1");
        emp.setApe1("ape11");
        emp.setApe2("ape12");
        emp.setEmail("nombre1.ape11@test.com");
        empleados.put("1", emp);
        emp = new Empleado();
        emp.setId("2");
        emp.setNombre("nombre2");
        emp.setApe1("ape21");
        emp.setApe2("ape22");
        emp.setEmail("nombre2.ape21@test.com");
        empleados.put("2", emp);
        emp = new Empleado();
        emp.setId("3");
        emp.setNombre("nombre3");
        emp.setApe1("ape31");
        emp.setApe2("ape32");
        emp.setEmail("nombre3.ape31@test.com");
        empleados.put("3", emp);
        emp = new Empleado();
        emp.setId("4");
        emp.setNombre("antonio");
        emp.setApe1("garcia");
        emp.setApe2("gonzalez");
        emp.setEmail("antonio.garcia.gonzalez@test.com");
        empleados.put("4", emp);
        emp = new Empleado();
        emp.setId("5");
        emp.setNombre("antonio");
        emp.setApe1("perez");
        emp.setApe2("gonzalez");
        emp.setEmail("antonio.perez.gonzalez@test.com");
        empleados.put("5", emp);
        emp = new Empleado();
        emp.setId("6");
        emp.setNombre("luis");
        emp.setApe1("garcia");
        emp.setApe2("ruiz");
        emp.setEmail("luis.garcia.ruiz@test.com");
        empleados.put("6", emp);
    }
    
    public Map<String, Empleado> getListaEmpleados(){

        return empleados;
    }

    /**
     * Devolvemos la lista de empleados coincidentes con TODOS los parámetros recibidos 
     * @return
     */
    public Map<String, Empleado> getListaEmpleadosCoincidentes(final String id, final String nombre, final String ape1, final String ape2, final String email){

        //Clonamos la lista original de emplados
        final List<Empleado> lstEmpleadosCoincidentes = new ArrayList<Empleado>();
        lstEmpleadosCoincidentes.addAll(empleados.values());
        
        //Vamos eliminando de la lista clonada aquellos que no coincidan con alguno de los criterios recibidos
        for (Iterator<Empleado> iterator = lstEmpleadosCoincidentes.iterator(); iterator.hasNext();) {
            Empleado emp = iterator.next();
            if (id!=null && !emp.getId().equals(id)) {
                iterator.remove();
            } else if (nombre!=null && !emp.getNombre().equals(nombre)) {
                iterator.remove();
            } else if (ape1!=null && !emp.getApe1().equals(ape1)) {
                iterator.remove();
            } else if (ape2!=null && !emp.getApe2().equals(ape2)) {
                iterator.remove();
            } else if (email!=null && !emp.getEmail().equals(email)) {
                iterator.remove();
            }
        }
        
        //Generamos el Map de empleados coincidentes
        Map<String, Empleado> mapEmpleadosCoincidentes = new HashMap<String, Empleado>();
        for (Empleado emp : lstEmpleadosCoincidentes) {
            mapEmpleadosCoincidentes.put(emp.getId(),emp);
        }
        
        //Devolvemos el Map de empleados coincidentes
        return mapEmpleadosCoincidentes;
    }
    
    public String getNumeroEmpleados(){
        //TODO: Parece que le molesta si devuelves un Integer...
        return Integer.toString(empleados.size());
    }
    
    public Empleado createEmpleado(final String id, final Empleado empleado){
        
        return empleados.put(id, empleado);
    }
    
    public Empleado readEmpleado(String id){
        
        return empleados.get(id);
    }
    
    public Empleado updateEmpleado(final String id, final Empleado empleado){
        
        final Empleado emp = empleados.get(id);
        emp.setNombre(empleado.getNombre());
        emp.setApe1(empleado.getApe1());
        emp.setApe2(empleado.getApe2());
        emp.setEmail(empleado.getEmail());
        return emp;
    }
    
    public Empleado deleteEmpleado(String id){
        
        return empleados.remove(id);
    }
}


Ejemplo de cliente java


Un pequeño ejemplo Java que hace uso del servicio web.

Clase AppCliente.java con el main:


package es.ine.sgtic;

import java.util.List;

import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;

import es.ine.sgtic.model.pojo.Empleado;
import es.ine.sgtic.model.pojo.Empleados;

public class AppCliente {

    private static ClientConfig clienteRestConfig;
    private static Client clienteRest;
    
    public static void main(String[] args) {

        //Instanciamos el cliente y lo configuramos para que acepte JSON 
        clienteRestConfig = new DefaultClientConfig();
        clienteRestConfig.getClasses().add(JacksonJaxbJsonProvider.class);
        clienteRestConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
        clienteRest = Client.create(clienteRestConfig);

        try {
           
            //Obtenemos el número de empleados
            obtenerNumeroEmpleados();
           
            //Obtenemos la lista de empleados
            obtenerListaEmpleados();
           
            //Realizamos diferentes búsquedas
            buscarEmpleadosCoincidentes(null,"antonio",null,null,null);
            buscarEmpleadosCoincidentes(null,"antonio",null,"gonzalez",null);
            buscarEmpleadosCoincidentes(null,null,"garcia",null,null);
            //TODO:  Como se peta porque la lista Empleados recibida sólo tiene un elemento
            //hemos tenido que ajustar en el server el parámetro de inicio siguiente:
            //<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            buscarEmpleadosCoincidentes(null,"antonio","garcia","gonzalez",null);
           
            //Recuperamos el empleado 1
            obtenerEmpleado("1");
           
            //Añadimos un empleado
            crearEmpleado("7","nombre7","ape71","ape72", "nombre7.ape71@test.com");
           
            //Obtenemos la lista de empleados por segunda vez
            obtenerListaEmpleados();
           
            //Actualizamos empleado
            actualizarEmpleado("7","nombre7","ape71","apeCAMBIADO", "nombre7.ape71@test.com");
           
            //Obtenemos la lista de empleados por segunda vez
            obtenerListaEmpleados();
           
            //Borramos un empleado
            borrarEmpleado("7");
           
            //Obtenemos la lista de empleados por segunda vez
            obtenerListaEmpleados();
           
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    private static void obtenerEmpleado(final String id) throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = "http://localhost:8080/rest-ws/rest/empleado/1";
        
        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.get(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta
        if (respuesta.getStatus() == 200) {
            Empleado emp = respuesta.getEntity(Empleado.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> obtenerEmpleado("+id+")");
            System.out.println(emp.getNombre() + " " + emp.getApe1() + " " + emp.getApe2() + " " + emp.getEmail());
            System.out.println("-----------------------------------------------------------");
        } else {
            throw new RuntimeException("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void obtenerNumeroEmpleados() throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = "http://localhost:8080/rest-ws/rest/empleado/count";

        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.get(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta y la tratamos
        if (respuesta.getStatus() == 200) {
            String numEmp = respuesta.getEntity(String.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> obtenerNumeroEmpleados()");
            System.out.println("Hay un total de " + numEmp + " empleados.");
            System.out.println("-----------------------------------------------------------");
        } else {
            throw new Exception("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void obtenerListaEmpleados() throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = "http://localhost:8080/rest-ws/rest/empleado/list";

        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.get(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta y la tratamos
        if (respuesta.getStatus() == 200) {
            final Empleados empleados = respuesta.getEntity(Empleados.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> obtenerListaEmpleados()");
            final List<Empleado> lstEmpleados = empleados.getLista();
            for (int c=0; c<empleados.getLista().size(); c++){
                System.out.println(lstEmpleados.get(c).getNombre() + " " + lstEmpleados.get(c).getApe1() + " " + lstEmpleados.get(c).getApe2()+ " " + lstEmpleados.get(c).getEmail() );
            }
            System.out.println("-----------------------------------------------------------");
           
        } else {
            throw new Exception("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void buscarEmpleadosCoincidentes(final String id, final String nombre, final String ape1, final String ape2, final String email) throws Exception {
       
        //Preparamos la URI
        final StringBuilder sbCriterios = new StringBuilder();
        if (id!=null){
            sbCriterios.append("id="+id);
        }
        if (nombre!=null){
            if(sbCriterios.length()>0) {
                sbCriterios.append("&nombre="+nombre);
            } else {
                sbCriterios.append("nombre="+nombre);
            }
        }
        if (ape1!=null){
            if(sbCriterios.length()>0) {
                sbCriterios.append("&ape1="+ape1);
            } else {
                sbCriterios.append("ape1="+ape1);
            }
        }
        if (ape2!=null){
            if(sbCriterios.length()>0) {
                sbCriterios.append("&ape2="+ape2);
            } else {
                sbCriterios.append("ape2="+ape2);
            }
        }
        if (email!=null){
            if(sbCriterios.length()>0) {
                sbCriterios.append("&email="+email);
            } else {
                sbCriterios.append("email="+email);
            }
        }

        System.out.println("http://localhost:8080/rest-ws/rest/empleado/find?"+sbCriterios.toString());
       
        final String EmpleadoURI = String.format("http://localhost:8080/rest-ws/rest/empleado/find?%s", sbCriterios.toString());

        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.get(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta y la tratamos
        if (respuesta.getStatus() == 200) {
            final Empleados empleados = respuesta.getEntity(Empleados.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> buscarEmpleadosCoincidentes("+id+","+nombre+","+ape1+","+ape2+","+email+")");
            final List<Empleado> lstEmpleados = empleados.getLista();
            for (int c=0; c<empleados.getLista().size(); c++){
                System.out.println(lstEmpleados.get(c).getNombre() + " " + lstEmpleados.get(c).getApe1() + " " + lstEmpleados.get(c).getApe2()+ " " + lstEmpleados.get(c).getEmail() );
            }
            System.out.println("-----------------------------------------------------------");
           
        } else {
            throw new Exception("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void crearEmpleado(final String id, final String nombre, final String ape1, final String ape2, final String email) throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = String.format("http://localhost:8080/rest-ws/rest/empleado/%s/%s/%s/%s/%s", id, nombre, ape1, ape2, email);
        
        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.post(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta
        if (respuesta.getStatus() == 200 || respuesta.getStatus() == 204) {
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> crearEmpleado("+id+","+nombre+","+ape1+","+ape2+","+email+") --> " + respuesta.getStatus());
            System.out.println("-----------------------------------------------------------");
        } else {
            throw new RuntimeException("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void actualizarEmpleado(final String id, final String nombre, final String ape1, final String ape2, final String email) throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = String.format("http://localhost:8080/rest-ws/rest/empleado/%s/%s/%s/%s/%s", id, nombre, ape1, ape2, email);
        
        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.put(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta
        if (respuesta.getStatus() == 200 || respuesta.getStatus() == 204) {
            Empleado emp = respuesta.getEntity(Empleado.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> actualizarEmpleado("+id+","+nombre+","+ape1+","+ape2+","+email+") --> " + respuesta.getStatus());
            System.out.println(emp.getNombre() + " " + emp.getApe1() + " " + emp.getApe2() + " " + emp.getEmail());
            System.out.println("-----------------------------------------------------------");
        } else {
            throw new RuntimeException("HTTP Error: " + respuesta.getStatus());
        }
    }
    
    private static void borrarEmpleado(final String id) throws Exception {
       
        //Preparamos la URI
        final String EmpleadoURI = String.format("http://localhost:8080/rest-ws/rest/empleado/%s", id);
        
        //Invocamos al WS obteniendo la respuesta
        final WebResource webResource = clienteRest.resource(EmpleadoURI);
        final ClientResponse respuesta = webResource.delete(ClientResponse.class);

        //Verificamos si tenemos una respuesta correcta
        if (respuesta.getStatus() == 200 || respuesta.getStatus() == 204) {
            Empleado emp = respuesta.getEntity(Empleado.class);
            System.out.println("-----------------------------------------------------------");
            System.out.println(">> borrarEmpleado("+id+") --> " + respuesta.getStatus());
            System.out.println("-----------------------------------------------------------");
        } else {
            throw new RuntimeException("HTTP Error: " + respuesta.getStatus());
        }
    }
}


Pojo empleados.java


package es.ine.sgtic.model.pojo;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
//JAX-RS mapea automáticamente las clases anotadas con JAXB a JSON
public class Empleados {

    List<Empleado> lista = new ArrayList<Empleado>();

    public List<Empleado> getLista() {
        return lista;
    }

    public void setLista(List<Empleado> lista) {
        this.lista = lista;
    }
} 


Pojo empleado.java


package es.ine.sgtic.model.pojo;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
//JAX-RS mapea automáticamente las clases anotadas con JAXB a JSON
public class Empleado {

    private String id;
    private String nombre;
    private String ape1;
    private String ape2;
    private String email;
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getNombre() {
        return nombre;
    }
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    public String getApe1() {
        return ape1;
    }
    public void setApe1(String ape1) {
        this.ape1 = ape1;
    }
    public String getApe2() {
        return ape2;
    }
    public void setApe2(String ape2) {
        this.ape2 = ape2;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}


Servidor web sobre Node.js que consume servicios web REST

Introducción

En esta entrada vamos a comentar una serie de 'extras' que se han añadido al portal 'miserver.js' de la entrada anterior.

El objetivo del ejemplo es tener un ejemplo básico que permita resolver las siguientescuestiones:
  • ¿Cómo montar un servidor integramente desarrollado con JavaScript y ejecutando sobre nodejs?
  • ¿Cómo modularizar el código JavaScript de nuestro portal en distintos ficheros JavaScript?
  • ¿Cómo invocar 'desde la capa de negocio' de este portal un servicio web REST corriendo en Tomcat?

Dependencias

Para poder correr el ejemplo hemos tenido que añadir algunos paquetes node, por lo que primero hemos instalado el gestor de paquetes de node (npm) con el comando:

sudo apt-get install npm

Seguidamente se ha añadido dos módulos (librerías o como se llame en este entorno):

  • Librería 'node-rest-client' para manejar llamadas a servicios REST.
  • Librería 'httpdispatcher' para gestionar el dispatcher de peticiones del servidor HTTP.
Se han instalado con los comandos:

npm install node-rest-client
npm install httpdispatcher

Código

Hemos modularizado el proyecto en tres ficheros:
  • miserver.js. Contiene lo necesario para levantar un servidor http sencillito que despacha peticiones http.
  • cliente-rest.js. Contiene lo necesario para invicar al servicio web rest anteriormente comentado.
  • render-html.js. Contiene lo necesario para montar las distitnas páginas del portal.
miserver.js

El código del servidor sería:


//Inportamos el módulo http
var http = require("http");
//Inportamos el módulo httpdispatcher
var dispatcher = require('httpdispatcher');

//Instanciamos el servidor
http.createServer(
 function(request, response) {
  //Llamamos al generador de paginas de respuesta
  dispatcher.dispatch(request, response);
}).listen(8888);

//Generamos la página de inicio
dispatcher.onGet("/", function(req, res) {
  var renderHtml = require( './render-html.js' );
  renderHtml.generaPaginaInicio(res);
});

//Generamos la pagina /numeroEmpleados
dispatcher.onGet("/numeroEmpleados", function(req, res) {
  var clienteRest = require( './cliente-rest.js' );
  clienteRest.getNumeroEmpleados(res);
});

//Generamos la pagina /listaEmpleados
dispatcher.onGet("/listaEmpleados", function(req, res) {
  var clienteRest = require( './cliente-rest.js' );
  clienteRest.getListaEmpleados(res);
});

//Generamos la pagina /formBuscaEmpleados
dispatcher.onGet("/formBuscaEmpleados", function(req, res) {
  var renderHtml = require( './render-html.js' );
  renderHtml.generaPaginaFormBuscaEmpleados(res);
});

//Generamos la pagina /formBuscaEmpleados
dispatcher.onGet("/listaEmpleadosCoincidentes", function(req, res) {
  var clienteRest = require( './cliente-rest.js' );
  clienteRest.getListaEmpleadosCoincidentes(req, res);
});

//Mensaje de aviso tras el arranque del servidor
console.log("Servidor funcionando en http://localhost:8888");


cliente-rest.js

El código de la libería encargada de las llamadas el servicio web REST sería:


//Inportamos el módulo node-rest-client
var Client = require('node-rest-client').Client;
var client = new Client();

//Inportamos el módulo url
var url = require('url')

//Metodo que devuelve el numero de empleados
var getNumeroEmpleados = function (res){
  client.get("http://localhost:8080/rest-ws/rest/empleado/count", function (data, r) {
      var renderHtml = require( './render-html.js' );
    renderHtml.generaPaginaNumeroEmpleados(res, data);
  });
};

//Metodo que devuelve la lista de empleados
var getListaEmpleados = function (res){
  client.get("http://localhost:8080/rest-ws/rest/empleado/list", function (data, r) {
    var renderHtml = require( './render-html.js' );
    renderHtml.generaPaginaListaEmpelados(res, data);
  });
};

//Metodo que devuelve la lista de empleados coincidentes
var getListaEmpleadosCoincidentes = function (req, res){
  var paramsPetic = url.parse(req.url,true).query;
  var urlPetic = "http://localhost:8080/rest-ws/rest/empleado/find?";
  if (paramsPetic.nombre!=""){
      urlPetic += "nombre=" + paramsPetic.nombre;
  }
  if (paramsPetic.ape1!=""){
      urlPetic += "&ape1=" + paramsPetic.ape1;
  }
  if (paramsPetic.ape2!=""){
      urlPetic += "&ape2=" + paramsPetic.ape2;
  }
  if (paramsPetic.email!=""){
      urlPetic += "&email=" + paramsPetic.email;
  }
  console.log(urlPetic);
  client.get(urlPetic, function (data, r) {
    var renderHtml = require( './render-html.js' );
    renderHtml.generaPaginaListaEmpelados(res, data);
  });
};

//Exportamos los metodos publicos
module.exports.getNumeroEmpleados = getNumeroEmpleados;
module.exports.getListaEmpleados = getListaEmpleados;
module.exports.getListaEmpleadosCoincidentes = getListaEmpleadosCoincidentes;


render-html.js

El código de la libería encargada de montar las páginas HTML sería:

var generaPaginaInicio = function (res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<?xml version='1.0' encoding='UTF-8'?>");
  res.write("<!DOCTYPE html>");
  res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
  res.write("<head>");
  res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
  res.write(" <meta name='language' content='es'/>");
  res.write("</head>");
  res.write("<body>");
  res.write("<h3> Primer miniportal en Node.js.</h3>");
  res.write(" <ul>");
  res.write("   <li><a href='/numeroEmpleados'>Numero de empleados</a></li>");
  res.write("   <li><a href='/listaEmpleados'>Lista de empleados</a></li>");
  res.write("   <li><a href='/formBuscaEmpleados'>Buscar de empleados</a></li>");
  res.write(" </ul>");
  res.write("</body>");
  res.end();
};

var generaPaginaNumeroEmpleados = function (res, data){

  //Montamos la página de respuesta
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<?xml version='1.0' encoding='UTF-8'?>");
  res.write("<!DOCTYPE html>");
  res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
  res.write("<head>");
  res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
  res.write(" <meta name='language' content='es'/>");
  res.write("</head>");
  res.write("<body>");
  res.write("<h3> Hay un total de "+data+" empleados.</h3>");
  res.write("</body>");
  res.end();
};

var generaPaginaFormBuscaEmpleados = function (res, data){

  //Montamos la página de respuesta
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<?xml version='1.0' encoding='UTF-8'?>");
  res.write("<!DOCTYPE html>");
  res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
  res.write("<head>");
  res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
  res.write(" <meta name='language' content='es'/>");
  res.write("</head>");
  res.write("<body>");
  res.write("<h3> Buscador de empleados.</h3>");
  res.write("<form action='/listaEmpleadosCoincidentes' method='get'>");
  res.write("  Nombre: ");
  res.write("  <input type='text' name='nombre'><br>");
  res.write("  Apellido 1: ");
  res.write("  <input type='text' name='ape1'><br>");
  res.write("  Apellido 2: ");
  res.write("  <input type='text' name='ape2'><br>");
  res.write("  EMail: ");
  res.write("  <input type='text' name='email'><br>");
  res.write("  <input type='submit' value='Submit'>");
  res.write("  </form>");
  res.write("</body>");
  res.end();
};

var generaPaginaListaEmpelados = function (res, empleados){

  //Montamos la página de respuesta
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<?xml version='1.0' encoding='UTF-8'?>");
  res.write("<!DOCTYPE html>");
  res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
  res.write("<head>");
  res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
  res.write(" <meta name='language' content='es'/>");
  res.write("</head>");
  res.write("<body>");
  res.write("<h3> tabla de empleados.</h3>");
  res.write("<table>");
  res.write(" <tr>");
  res.write("   <th>ID</th>");
  res.write("   <th>NOMBRE</th>");
  res.write("   <th>APELLIDO 1</th>");
  res.write("   <th>APELLIDO 2</th>");
  res.write("   <th>EMAIL</th>");
  res.write(" </tr>");
  for (var i = 0; i < empleados.lista.length; i++) {
    var emp = empleados.lista[i];
    res.write(" <tr>");
    res.write(" <td>");
    res.write(emp.id);
    res.write(" </td>");
    res.write(" <td>");
    res.write(emp.nombre);
    res.write(" </td>");
    res.write(" <td>");
    res.write(emp.ape1);
    res.write(" </td>");
    res.write(" <td>");
    res.write(emp.ape2);
    res.write(" </td>");
    res.write(" <td>");
    res.write(emp.email);
    res.write(" </td>");
    res.write(" </tr>");
  }
  res.write("</table>");
  res.write("</body>");
  res.end();
};

//Exportamos los metodos publicos
module.exports.generaPaginaInicio = generaPaginaInicio;
module.exports.generaPaginaNumeroEmpleados = generaPaginaNumeroEmpleados;
module.exports.generaPaginaListaEmpelados = generaPaginaListaEmpelados;
module.exports.generaPaginaFormBuscaEmpleados = generaPaginaFormBuscaEmpleados;


Ejecución y pruebas

En la carpeta donde tengamos los tres ficheros javascrip del ejemplo tendremos que levantar el servidor con:

nodejs miserver.js

Seguidamente abriremos un lavegador y teclearemos la URL:

http://localhost:8888/

Con esto ya tendríamos el portal funcionando y podríamos hacer pruebas

Nota. En el ejemplo hemos usado un servicio web RESTful montado sobre Jersey que comentaremos en el próximo post.









miércoles, 16 de marzo de 2016

Introducción a las aplicaciones híbridas


Aplicaciones Nativas

Hasta hace poco tiempo el desarrollo de aplicaciones para móviles y tablets implicaba tantos desarrollos paralelos como plataformas destino se quisiesen abarcar. Ello se debe a que cada plataforma tiene su propio sistema operativo y su propio lenguaje de programación:
  • iOS: Objective C
  • Android: Java
  • Windows: C# y Visual Basic .NET.
  • BlackBerry 10: C++
El desarrollo de una aplicación nativa en varios de estos sistemas tiene una desventaja fundamenta, y es que es muy caro al precisarse equipos de desarrollo expertos en cada plataforma. Por otro lado tiene también ventajas, la ventaja de acceder directamente al hardware del movil/tablet, acceder directamenet a sus librerías gráficas, etc, que permite aplicaciones de alto rendimiento.

Aplicaciones Hibridas


Las aplicaciones hibridas son ideales para el desarrollo de aplicaciones para móviles, dado que:
  • Se desarrollan 100% en HTML 5, CSS 3 y JavaScript (son páginas web).
  • Se empaquetan (.apk, .iap, .xap) para cada plataforma específica con el lanzador (un trozo de código nativo) y los componentes específicos necesarios para cada plataforma.
  • Se ejecutan en el WebView, que viene a ser un navegador interno incorporado en todas las plataformas.
Así tendremos que una app híbrida funciona sin mas en varias plataformas, con lo que se ahorran costes.

Si nuestra aplicación no pudiese acceder a los dispositivos específicos de nuestro movil/tablet (gps, brújula, acelerómetro, cámara, conexión a internet, almacenamiento, etc) la aplicación quedaría muy coja, ahí es donde entra Apache Cordova.

Apache Cordova


Apache Cordova es un framework que permite a las aplicaciones híbridas (HTML 5, CSS 3 y JavaScript) acceder a las funciones nativas de cada plataforma (movil/tablet) a través de un API JavaScript único. El framework está compuesto por multitud de plugins, que 'importaremos' según las necesidades de nuestra app.

Apache Cordova permite por tanto que nuestra apicación hibrida trabaje con el 'hardware' de múltiples plataformas indistintamente, sin cambiar una sola línea de código.

Las plataformas soportadas por Apache Cordova son:
  • Android.
  • BlackBerry 10.
  • iOS
  • Ubuntu.
  • Amazon Fire OS.
  • Windows Phone 8 y 8.1
  • Windows 8.1 y 10.
  • Tizen.

Introducción a Ionic

Es el framework con el que construimos el código fuente HTML 5, CSS 3 y JavaScript que se ejecuta en el WebView. Tiene múltiples ventajas:
  • Ionic utiliza Apache Cordova como wrapper o capa de abstracción del SO existente en el movil/tablet donde ejecutará el aplicativo.
  • Ionic está pensado para la visualización en dispositivos móviles, no es una librería responsive tipo Bootstrap que se adapte a distintos tamaños de pantalla. Ionic incluye una completa librería de componentes, estilos y animaciones que simulan el aspecto nativo de las distintas plataformas. Estos componentes adoptarán automáticamente la apariencia nativa del dispositivo en el que si visualicen.
  • Ionic utiliza Angular como framework JavaScript. A priori con Ionic podríamos crear una aplicación sin necesidad de escribir código JavaScript únicamente con HTML5 y CSS3, pero si queremos que nuestra aplicación tenga contenidos dinámicos, por ejemplo para mostrar una lista de clientes a partir de los datos descargados desde cierto servicio web tendríamos que usar JavaScript con AngularJS.
  • Trabaja en conjunto con la tecnología conocida como Bower que funciona como repositorio de modulos JavaScript. Con este framework de dependencias JavaScript nos permite instalar módulos o plugins más facilmente para ser utilizados por AngularJS

Introducción a AngularJS

AngularJS es un framework JavaScript para el desarrollo de aplicaciones web en el lado del cliente que propone un patrón MVC (Modelo, Vista, Controlador) que nos permite estructurar el código JavaScript de forma eficiente.

Algunas de las características más interesantes del framework son:
  • El servidor proporciona los contenidos estáticos (plantillas) y la información que se va a representar (modelo) y es el cliente el encargado de mezclar la información del modelo con la plantilla para generar la vista.
  • Podemos sincronizar el modelo y la vista automáticamente utilizando ciertas directives (ng-model en el ejemplo) del framework. Esta sincronización es bidireccional, es decir, la información se sincroniza tanto si cambia el valor en la vista como si lo hace el valor en el modelo.
  • Podemos usar o definir 'directives' que son extensiones de la sintaxis de HTML5 que nos permiten crear o ajustar componentes. Por ejemplo podemos modificar el comportamiento de los elementos input para que cambien el color de fondo cuando obtiene o pierde el foco.
  • Podemos crear filters nos permiten modificar el modo en el que se va a presentar la información al usuario.
  • Podemos usar services que son los encargados de las comunicaciones con el servidor para subir/bajar datos.
  • Podemos usar controllers para tratar los datos anteriores y mostrarlos adecuadamente en pantalla.
  • Podemos crear aplicaciones SPA (Single Page Applications), que son aplicaciones que recargan una parte de la página dinámicamente, sin que se tenga que recargar toda la página. Esto permite hacer una aplicación web más rápido y fácil.

Introducción a Bower

Bower por es un administrador de paquetes, que nos permite definir qué frameworks de JavaScript y CSS necesita nuestra aplicación (Ionic, Bootstrap, AngularJS, Node.js, NPM, GIT, etc) y bower se encarga de gestionar las dependencias propias de éstos paquetes, averiguando la versión necesaria para todos. Si aparecen conflictos, Bower genera un aviso.

Mediante el uso de Bower no tendremos que descargar y copiar a mano todas esas dependencias en nuestros proyectos.
Bower requiere NPM, el gestor de paquetes de Node.js. También necesitaremos tener instalado GIT ya que generalmente las dependencias las obtendremos directamente de este tipo de repositorios, encontrándose muchas de ellas en GitHub.

Introducción a Node.js y NPM

Node.js es tanto un framework de programación JavaScript orientado a eventos, como un motor de ejecución basado en JavaScript V8 de Google (el motor JavaScript de Google Chrome).

Node.js implementa los protocolos de comunicaciones más habituales: HTTP, DNS, TLS, SSL, etc. También implementa operaciones de entrada/salida muy ligeras y potentes.

El rendimiento de las aplicaciones basadas en Node.js es tan bueno que está reemplazando a Apache como servidor web en entornos de alto rendimiento, dado que minimiza el número de conexiones y el consumo de memoria.

Apache crea un nuevo thread por cada solicitud del usuario, cuando hay mucha concurrencia el rendimiento se degrada porque hay muchos threads. Una aplicación para Node.js trabaja con un único thread. Si en la aplicación existe una operación bloqueante (I/O por ejemplo), Node.js creará entonces otro hilo en segundo plano, pero no lo hará sistemáticamente por cada conexión como haría Apache. En teoría Node.js puede mantener tantas conexiones como número máximo de archivos descriptores (sockets) soportados por el sistema.


Un problema de Node.js es que debido a su arquitectura de usar sólo un hilo también que sólo puede usar una CPU. Un método para usar múltiples núcleos sería iniciar múltiples instancias de Node en el servidor y poner un balanceador de carga delante de ellos.

La potencia anteriormente comentada ha provocado que JavaScript empiece a ser ampliamente usado en el lado del servidor, donde es interpretado y ejecutado por el motor de Node.js.

Instalación de Node.js

curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash -
sudo apt-get install -y nodejs

Verificación de la versión instalada de Node.js

nodejs -v

Ejemplo de un servidor web 'miserver.js' con Node.js

//Inportamos el módulo http
var http = require("http");
//Definimos una variable contador
var contador=0;
//Instanciamos el servidor y le pasamos por parametro
//la función callback anónima que se invocará en cada
//petición
http.createServer(
function(request, response) {
console.log("Nueva solicitud");
contador++;
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<?xml version='1.0' encoding='UTF-8'?>");
response.write("<!DOCTYPE html>");
response.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
response.write("<head>");
response.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
response.write(" <meta name='language' content='es'/>");
response.write("</head>");
response.write("<body>");
response.write(" <h1>Hola, eres el visitante n\u00FAmero "+ contador +".</h1>");
response.write("</body>");
response.end();
}).listen(8888);
//Mensaje de aviso tras el arranque del servidor
console.log("Servidor funcionando en http://localhost:8888");

Ejecutamos el aplicativo JavaScript que implementa nuestro miniservidor

nodejs miserver.js

Probamos desde el navegador mediante la URL: http://localhost:8888/

Nota. Necesitamos Node.js porque Ionic lo usa tanto en su interfaz de comandos (CLI) como para construir tareas.

Introducción a GIT

Es el sistema de control de versiones …. <PENDIENTE. Continuará...>

Referencias

martes, 15 de marzo de 2016

Ajustar apache2 con SSL, proxy y headers

Hola

En esta entrada vamos a ver cómo simular en nuestro PC un entorno de producción donde tendríamos corriendo un servidor Apache y varios servidores Tomcat balanceados.

El trabajo del servidor Apache es ejercer de intermediario entre los usuarios y el servidor o servidores tomcat balanceados, encriptando las comunicaciones con el usuario final (https) y manteniendo comunicaciones abiertas (http) con los diferentes Tomcats balanceados.

Cuando un usuario solicita una página mediante 'https://localhost/portal-xxx' al Apache, éste le solicitará un certificado electrónico, verificará si el certificado está emitido por alguna de las autoridades de certificación admitida, extraerá la parte pública del certificado (pem) y la enviará en la cabecera de la petición, junto con el resto de la solicitud, al servidor Tomcat del segundo plano 'http://localhost:8080/portal-xxx'.

Reproducir este entorno en desarrollo es muy fácil y permite probar nuestras aplicaciones sin tener que introducir en ellas nada que simule este comportamiento.

Los pasos a dar para montar todo esto son:

1) Generamos el certificado del servidor, dado que todo servidor https debe tener su propio certificado para poder realizar el handshaking asociado a toda comunicación ssl (https). En este caso el cdertificado será autofirmado, por lo que el navegador no confiará en nuestro 'https://localhost/portal-xxx' y deberemos ignorar la adveretncia de seguridad en las pruebas.

1.1) Tenemos que instalar openssl:

   sudo apt-get install openssl

1.2) Para generar un certificado de servidor autofirmado de 2048 bits y 10 años de duración lanzaremos el siguiente comando:

   openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt

Durante la generación nos pedirán los siguientes parámetros:

   Country Name (2 letter code) [AU]:ES
   State or Province Name (full name) [Some-State]:Madrid
   Locality Name (eg, city) []:Madrid
   Organization Name (eg, company) [Internet Widgits Pty Ltd]:EMPRESA
   Organizational Unit Name (eg, section) []:DEPARTAMENTO
   Common Name (e.g. server FQDN or YOUR name) []:localhost
   Email Address []:egdp1970@gmail.com


1.3) Guardamos los 2 ficheros generados, en mi caso los dejaré en la carpeta 'apache-keys' de mi $HOME

1.4) También deberemos preparar un fichero con los certificados raiz admitidos por nuestro servidor, adjunto el fichero 'certificadosCA.crt' que contiene los certificados raiz admitidos por la SEDE electrónica del INE.

2) Instalación de apache2 + mod_ssl + mod_headers:

Instalamos apache2

   sudo apt-get install apache2

Activamos los mods ssl y headers, estos dos mods están ahora incluídos en el apache2-common, por lo que sólo tenemos que activarlos en nuestro servidor.

   sudo a2enmod ssl

   sudo a2enmod headers

3) Instalación del mod_proxy:

   sudo apt-get install libapache2-mod-proxy-html
   sudo apt-get install libxml2-dev


Activamos el mod proxy

   sudo a2enmod proxy proxy_http

4) Creamos el fichero de configuración asociado a nuestro sitio /etc/apache2/sites-available/portal-xxx.conf como una copia del fichero /etc/apache2/sites-available/default-ssl.conf

  sudo cp default-ssl.conf portal-xxx.conf

Editamos  el fichero 'portal-xxx.conf'

sudo gedit portal-xxx.conf

Ajustamos el contenido del fichero: /etc/apache2/sites-available/portal-xxx.conf con todo: las opciones de seguridad, las opciones de gestión de cabeceras y las opciones de redirección al tomcat. El contenido del bloque '<VirtualHost _default_:443>' debería ser:

<IfModule mod_ssl.c>
 <VirtualHost _default_:443>
  ServerAdmin x9000004.empresas@ine.es
  DocumentRoot /var/www/html
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  SSLEngine on
  SSLCertificateFile    /home/miusuario/apache-keys/localhost.crt
  SSLCertificateKeyFile /home/
miusuario/apache-keys/localhost.key
  SSLCACertificateFile  /home/
miusuario/apache-keys/certificadosCA.crt
  #SSLVerifyClient none
  #SSLVerifyClient optional
  SSLVerifyClient require
  RequestHeader set SSL_CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
  RequestHeader set SSL-CLIENTCERT-PEM "%{SSL_CLIENT_CERT}s"
  RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s"
  RequestHeader set SSL_CLIENT_CERT_CHAIN_0 "%{SSL_CLIENT_CERT_CHAIN_0}s"
  SSLVerifyDepth 2
  SSLOptions +ExportCertData
  SSLInsecureRenegotiation on
  ProxyPass          /portal-xxx http://localhost:8080/
portal-xxx/
  ProxyPassReverse   /
portal-xxx http://localhost:8080/portal-xxx/
 </VirtualHost>
</IfModule>


5) Activamos el nuevo sitio ssl en el servidor:

   sudo a2ensite localhost-ssl


6) Paramos el servidor apache

   sudo service apache2 stop

7) Arrancamos el servidor apache

   sudo service apache2 start
   #sudo service apache2 restart


Y eso es todo, ahora podríamos levantar nuestro proyecto en depuración en nuestro IDE con su Tomcat escuchando por el puerto 8080 y realizar el tratamiento del certificado aportado por el usuario (validarlo, verificar su estado de revocación, romperlo para extraer sus datos, etc).

Un saludo