Programando con Node-Webkit. 3: cliente-servidor

nodewebkitHace unos días hablaba de montar una aplicación cliente-servidor rápidamente usando ssh para ejecutar comandos remotos.

Bueno, funcionar funciona, pero tiene algunos inconvenientes como por ejemplo depender de usuarios de sistema con los que hacer login. Disponiendo de tiempo y ganas sería mejor desarrollar nuestro propia app de servidor con nuestro propio sistema de autenticación, y mejor aun si además con ello conseguimos que nuestra aplicación sea totalmente portable.

Puede haber mil formas de hacerlo, pero ya que estamos metidos con node-webkit le daremos una vuelta a una de sus alternativas en la vertiente de server: express.js.

Al igual que con node-webkit estaremos utilizando node.js, por lo que el desarrollo de la aplicación será casi idéntico. La diferencia es que en vez de usar un webkit estaremos creando un mini servidor web con el que se comunicará nuestra aplicación cliente.

Pasos previos

Antes de empezar a desarrollar necesitaremos una pequeña preparación de nuestro entorno (básicamente instalar node y express). Si estás en ubuntu bastará con hacer esto:

sudo apt-get install nodejs nodejs-dev nodejs-express

Para otras distribuciones es posible que también esté disponible en el gestor de paquetes, pero si no lo estuviese podéis descargar node.js desde su web:

http://nodejs.org/download/

 

Estructura de la aplicación

Hasta aquí sigue siendo bastante parecido a node-webkit: creamos una carpeta y dentro un archivo llamado package.json

{
  "name": "testserver",
  "description": "Test Server App",
  "version": "0.0.1",
  "private": true,
  "scripts": {
       "start": "node ./server.js"
   },
   "dependencies": {
       "express": "3.x"
   }
}

Los campos son parecidos a los de node-webkit, como veis.

Si vamos a la carpeta donde hemos creado este archivo y ejecutamos npm install se nos instalarán las dependencias necesarias dentro de la subcarpeta node_modules (en este caso se instalaría “express”).

Para este artículo se ha usado express.js versión 3, el uso de la versión 4 es ligeramente diferente.

Creando el servidor

Creamos un archivo server.js junto con nuestro package.json.


var express = require('express'),
    app = express(),
    http = require("http"),
    server = http.createServer(app);

app.configure(function() {
    app.use(app.router);
});

app.get('/', function(req, res) {
    res.send("hola!");
});

server.listen(3000, function() {
    console.log("Node server running on http://localhost:3000");
});

En el primer párrafo importamos express y creamos instancias de nuestra aplicación y un servidor http asociado a ella.

En el segundo párrafo estamos definiendo la configuración de la aplicación express… y aquí entra en juego por primera vez el concepto de “middleware”, del que hablaré más abajo.

En el tercer párrafo lo que estamos haciendo es definir un handler para una petición tipo GET. Cuando un navegador web (o una aplicación) haga un GET con el parámetro “/” (o sea, a lo que sería la raíz del servidor web) recibirá un “hola!”.

Por último tenemos un server.listen donde indicamos a nuestra aplicación que se ponga a escuchar en el puerto que le pasemos por parámetro, en este caso el 3000.

Arrancando la aplicación

Con el mini código de ejemplo de arriba tenemos ya suficiente para probar si la cosa funciona. Vamos a la carpeta donde tenemos nuestro server.js y ejecutamos:

node ./server.js

Nos aparecerá el string que hemos puesto en la función console.log dentro del callback de server.listen:

Node server running on http://localhost:3000

Y si vamos con nuestro navegador a http://localhost:3000 veremos que efectivamente aparece en nuestra pantalla el “hola!”.

Middleware

Y qué es eso del middleware?

Mira tu server.js e imagina todos los handlers de llamadas app.get que hayas configurado fuese una secuencia de condiciones IF. Al recibir una petición, express irá recorriendo estos handlers del primero al último, y entrará por aquel que cuadre con el tipo de petición.

Ahora imagina que tienes un handler configurado para responder a una petición GET, pero es que además de responder a esa (o cualquier otra) petición necesitas que se ejecute otra cosa antes. Por ejemplo puede que quieras hacer logging de la solicitud.


app.use(function(req, res, next) {
  console.log('%s %s', req.method, req.url);
  next();
});

app.get('/', function(req, res) {
    res.send("hola!");
});

Si ahora accedes con tu navegador a http://localhost:3000, tu petición irá igualmente por el handler app.get, pero antes pasará por ese app.use.

La idea es que podemos colocar handlers (middleware) en cascada en cualquier punto que nos interese para implementar funciones adicionales, previas (si lo ponemos encima) a la entrada al handler que gestionará la solicitud finalmente.

Cuando hablé de middleware estaba hablando de la configuración de la aplicación en app.configure. Lo que podemos hacer ahí es añadir middleware a la función de callback, de forma que se aplique antes de todos nuestros handlers, y en el orden en que lo pongamos.

En el ejemplo había un “app.use(app.router);”. Esto nos añade el middleware “router”, que lo que hace es montar las rutas de nuestra aplicación. Realmente esto se añade automáticamente cuando llamamos a un handler app.get, app.post, etc… así que en el ejemplo es redundante, pero como contaba antes el orden importa, y por tanto si estuviésemos añadiendo ahí más middleware sí podría interesarnos declararlo explícitamente para forzar su orden respecto al resto.

Otro ejemplo de middleware que podemos declarar ahí es methodOverride, que nos permite crear handlers que simulen una petición PUT (por ejemplo desde un envío de un formulario) como app.put, en vez de implementarlo como app.post.

Implementando nuestra funcionalidad cliente-servidor

OK, todo esto es muy interesante, pero lo que quiero es comunicar mi aplicación cliente con esta aplicación servidor. ¿Cómo hago eso?.

Pues puedes definir la lógica que te apetezca, pero lo más sencillo quizá es simplemente crear handlers para llamar a las distintas funcionalidades del server, y pasar la respuesta a nuestro cliente con un JSON. O sea, definir nuestro API particular a base de URLs y parámetros.

Para mayor congruencia (y sobre todo portabilidad) lo ideal sería implementar la funcionalidad del server en javascript, pero como ya tenía mi script bash creado para comunicar a través de SSH, y dado que éste me está devolviendo ya un JSON, voy a usar eso.

Un ejemplo de handler podría ser este:


app.get('/test', function(req, res) {
    var exec = require('child_process').exec;
    var child;
    child = exec(oscript+" -"+req.param("op")+" "+req.param("node"), function (error, stdout, stderr) {
      res.send(stdout);
      if (error !== null) {
        console.log('exec error: ' + error);
      }
    });    
});

Dado que estamos pasando los parámetros de la llamada como parámetros de un exec sería tremendamente apropiado sanear esos strings para no pedir a gritos una inyección, pero se entiende la idea.

Cuando hagamos desde nuestro cliente un GET a la URL “http://localhost:3000/test?op=a&node=b” estaremos provocando la ejecución del script que hayamos definido en la variable oscript con los parámetros “-a b”. La salida STDOUT del script será lo que se devuelva por http, y en este caso el script está devolviendo un JSON.

Conectando desde el cliente

Una vez que tengamos definido nuestro API en el servidor tenemos que implementar las llamadas al mismo desde nuestro cliente node-webkit:


function runtest(testname,nodename) {
         $.getJSON(serverhost+"/test?op="+testname+"&node="+nodename,{format:"json"})
          .done(function(data) {
                console.log(data);
		evalresponse(data);
          });
}

Cuando ejecutemos la función runtest se hará un GET a nuestro servidor express.js y se obtendrá un JSON, que se guardará en el objeto data. Este objeto se lo podemos pasar a otra función para hacer algo con el resultado, accediendo a los datos como un objeto javascript en vez de tener que andar parseando strings de texto.

Voilà. Esa es la base. A partir de ahí, explotarla para implementar todo lo que se nos ocurra, añadiendo si queremos sistemas de autenticación o comunicación por https.

  • Jose Torrens

    Buenas tardes. Ando probando el node webkit. y quiero obtener un JSON desde una URL. para luego mostrarlo en un Select. Pero quiero hacerlo en JS, nada de jquery. Sabras como iluminarme?

    • Buenas.

      Con JS a pelo puedes usar xmlhttprequest para obtener el JSON desde la URL, y luego convertirlo en un objeto JS con la función JSON.parse(variablejson).

      • Jose Torrens

        Sí. Ya lo hice, tuve ciertos problemas de cross domain, pero solvente. Me fue genial empezare a hacer cosas mas avanzadas. Tienes algún contenido para compartir de ese tema?.

        • Creo que soy demasiado vago para ir con JS a pelo 😀 suelo tirar de librerías como jquery siempre que puedo.