Conectar LearningML-editor con LearningML Scratch

Ya hemos visto en una entrada anterior que la plataforma LearningML consta de dos aplicaciones:

  • el editor de modelos de Machine Learning (learningml-editor) y
  • el editor de programas (lml-scratch).

En esa misma entrada se explicó como montar un entorno de desarrollo para poder trabajar localmente en la construcción de las aplicaciones.

Sin embargo, queda un pequeño detalle que resolver para disponer de un entorno de desarrollo completo: conectar ambas aplicaciones para que los modelos de Machine Learning (ML) que se construyen en lml-editor se puedan usar en los bloques de lml-scratch.

En efecto, si pruebas a hacer lo siguiente en tu entorno de desarrollo:

  1. ejecutar ambas aplicaciones en sus respectivos servidores de desarrollo,
  2. construir un modelo de ML con learningml-editor y
  3. abrir el editor de programación picando en el botón del gatito,

comprobarás que, aunque se abre lml-scratch, los modelos de ML no están disponibles. ¿Por qué sucede esto? ¿cómo podemos arreglarlo? Estas serán las preguntas que responderemos en esta entrada.

¿Por qué no está disponible el modelo de ML en el editor de programación Scratch?

La estrategia que he usado para poder usar en lml-scratch el modelo construido con learningml-editor, se basa en dos mecanismo ofrecidos por los navegadores web: El LocalStorage y el Broadcast Channel. El primero sirve para almacenar datos en el navegador que pueden ser accedidos por aplicaciones que se ejecutan en distintas pestañas. Mientras que el segundo sirve para enviar mensajes entre aplicaciones que se ejecutan también en distintas pestañas del navegador.

Cuando se crea un nuevo modelo con learningml-editor, este coloca en el LocalStorage información sobre el tipo de modelo que se ha creado, para que lml-scratch sepa qué bloques de Machine Learning pueden usarlo. Por otra parte, cuando se usa un bloque de Machine Learning en lml-scratch (por ejemplo un bloque para clasificar un texto) este envía un mensaje a lml-editor solicitándole el resultado. Cuando lml-editor lo recibe, realiza la operación (por ejemplo clasificar el texto) y la devuelve al bloque que la pidió.

Pero resulta que los mecanismos de LocalStorage y Broadcast Channel solo funcionan entre pestañas del navegador que han sido cargadas desde el mismo dominio. Y aquí es donde radica el problema: los servidores de desarrollo de learningml-editor y de lml-scratch se levantan en puertos distintos, de manera que a la primera aplicación se accede desde http://localhost:4200, y a la segunda desde http://localhost:8601. Y estas dos urls son consideradas por el navegador web como distintos dominios. ¿Vaya cagada, no?

¿Cómo podemos superar este problema?

Pues está claro que la única manera de resolver el problema es sirviendo ambas aplicaciones desde el mismo dominio. ¿Cómo podemos hacerlo? Tenemos dos posibilidades:

  1. Crear un desplegable de cada una de la aplicación (ambas aplicaciones son estáticas, es decir, están compuestas únicamente por código HTML, Javascript y CSSs), y desplegarlas en un servidor web (apache o nginx, por ejemplo) de manera que ambas sean servidas desde el mismo dominio (por ejemplo: http://mi.dominio/learningml y http://mi.dominio/scratch).
  2. Colocar los servidores de desarrollo detrás de un reverse proxy al que se acceda desde un mismo dominio.

La primera solución es adecuada para un entorno de producción pero no es operativa en un entorno de desarrollo, pues tendríamos que reconstruir los desplegables y colocarlos en su sitio cada vez que hagamos una modificación al código y queramos comprobar como funciona.

Así que solo nos queda la segunda :-). Veámos en que consiste con más detalle.

Un reverse proxy no es más que un servidor que realiza consultas a distintos servidores, según la url solicitada por el cliente (aquí cliente significa navegador web). Acto seguido, devuelve al cliente la respuesta obtenida del servidor como si la hubiese construido él mismo. Es decir, el cliente no conoce todo este batiburrillo que acabo de contar. El siguiente esquema ayuda a entender el funcionamiento de un reverse proxy.

Fuente: https://commons.wikimedia.org/wiki/File:Reverse_Proxy.png

Lo que nos interesa de este chisme es que peticiones que llegan con el mismo dominio pero con distintas rutas, se pueden resolver en servidores que escuchan en distintos puertos. De esa manera, para el cliente que realiza la petición original, todas las respuestras provienen del mismo dominio, que es lo que intentamos conseguir para que el navegador web (es decir, el cliente) nos permita utilizar los mecanismos de LocalStorage y Broadcast Channel.

Existen muchas soluciones para desplegar un reverse proxy. Yo contaré la que uso para el desarrollo de LearningML, basada en nginx, la cual es suficiente para trabajar en los problemas que requieren conexión entre learningml-editor y lml-scratch en desarrollo.

La solución es tan sencilla como instalar un servidor nginx, y configurarlo (archivo nginx.conf) de la siguiente manera:

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;

        proxy_buffering off;

        location / {
            proxy_pass http://localhost:4200/;
        }
        location /scratch/ {
            proxy_pass http://localhost:8601/;
        }
    }

    include servers/*;
}

Observa que la parte interesante está en la sección server. Viene a decir que cuando el servidor proxy reciba una petición http://localhost:8000/, consulte al servidor de desarrollo de learningml-editor (recuerda que este responde a http://localhost:4200), y devuelva el resultado al cliente. Y que cuando reciba una petición http://localhost:8000/scratch, consulte al servidor de desarrollo de lml-scratch (recuerda que responde a http://localhost:8601), y devuelva el resultado al cliente (es decir, al navegador web).

De esta manera hemos conseguido que el cliente reciba las dos aplicaciones: learningml-editor y lml-scratch desde el mismo dominio, a saber: localhost:8080, y por tanto, que funcionen los mecanismos LocalStorage y Broadcast Channel, necesarios para que ambas aplicaciones se comuniquen.

Y eso es todo. Esta es la solución que se me ocurrió para que los mecanismos LocalStorage y Broadcast Channel funcionansen en mi entorno de desarrollo. No sé si habrá otra más sencilla. Así que si a alguno de vosotros se os ocurre, por favor, no dudéis en contármela. De todas formas, hay que tener en cuenta que normalmente se trabaja de forma independiente en cada una de las aplicaciones, y en ese caso no es necesario utilizar el reverse proxy. Solo cuando estemos liados con problemas relacionados con el uso del modelo de Machine Learning desde lml-scratch, será necesario usarlo.