domingo, 11 de febrero de 2018

Resolver por nombre de dominio en docker

Recientemente he adquirido cierta práctica dockerizando mis trabajos, encuentro que es una manera muy cómoda de trabajar, independizando cada proyecto de los otros.  Como no podía ser de otra manera he intentado dar el siguiente paso y poner esto de docker en práctica no solo para desarrollo sino para staging o incluso producción.

Mientras que en mi ordenador local no me molesta tener que acceder al proyecto con localhost:80xx no creo que los visitantes encontraran esto muy práctico, amén de las penalizaciones que seguro que los motores de búsqueda (si acaso encontraran esas webs) podrían hacer.

Así que, este fin de semana me planteé como reto intentar poner un contenedor maestro que tuviera una copia de nginx y que resolviera, por nombre de dominio, dirigiendo el tráfico al contenedor adecuado.

Esto no es a priori una tarea trivial, al menos no trabajando con docker-compose. Por lo que me dispuse a hacer diferentes pruebas siguiendo este artículo https://www.thepolyglotdeveloper.com/2017/03/nginx-reverse-proxy-containerized-docker-applications/.

El truco, si se le puede llamar así, consiste en tener claro qué recursos son compartidos, en este caso los nombres de los contenedores, los puertos que exponen cada uno y como no, las redes que internamente crea docker para cada grupo de contenedores (dentro del mismo docker-compose.yml).

He creado un repositorio totalmente funcional OTB. https://github.com/jlaso/docker-nginx-reverseproxy-domain-name

Para explicar un poco el funcionamiento, decir que la madre del cordero se haya en como redirige el tráfico nginx. Veamos este archivo de configuración:

upstream prj-one {
    server prj_one:80;
}

server {
    listen 80;
    server_name one.web.dev;

    location / {
        proxy_pass         http://prj-one;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}


Lo primero que hacemos es crear un upstream al que le damos un nombre (arbitrario, pero con sentido)  que luego usaremos en el proxy_pass (más abajo).  Importante que el nombre del server de ese upstream sea exactamente el mismo que definamos en hostname en ese contenedor.

Por si aún no lo has visto, el puerto es 80 tanto en el listen (justo queremos que sean puertos estándar), pero también lo es en la cláusula server, porque la comunicación con ese container a nivel interno será por el puerto 80 (el puerto que exponemos en el Dockerfile).

No sé si se requieren más explicaciones más allá de que lo pruebes y comentes.

domingo, 9 de julio de 2017

deymfony Castellón 2017

El día 30 de Junio de 2017 empezó la conferencia española de Symfony en Castellón. El año pasado no pude ir a Madrid pero este no me la quería/podía perder. Siendo en mi misma comunidad y en el mismo lugar donde todo esto de Symfony empezó para mi, en la segunda edición de deSymfony (2013) ... no me la podía perder.
Tengo que agradecer primero que nada que mi empresa Digilant corriera con mis gastos de asistencia, cada día son más las empresas que se conciencian de que un foro como una conferencia así no es una ocasión que se deba dejar escapar.

El primer día fue genial para mi, lleno de charlas interesantes y descansos con networking a tope. La calidad de la restauración para mi gusto aprobado justito. No así la cena con alguno de los ponentes en la que pude disfrutar muchísimo.

Noticias como la que Javier Eguiluz nos soltó en su charla me sentaron como un jarro de agua fría. No me gustan los cambios y últimamente tengo la sensación de que la gente de Symfony no para de introducir mejoras. En fin, habrá que montarse en el tren de Symfony Flex y la versión 4 y darle una oportunidad a todas las mejoras de rendimiento, performance y sobre todo tamaño que nos vendió tan bien Javier.




La siguiente ponencia fue la relacionada con desarrollo de aplicaciones CLI profesionales, a cargo del afamado Raúl Fraile. Me lo pasé muy bien con los ejemplos. No tenía ni idea de que se podían hacer fotogramas de Star Wars en la consola, con sonido y todo. Al margen de eso, me pareció muy interesante poder abordar la creación de una aplicación CLI, encapsularla y dotarla de seguridad y posibilidad de actualización. No descarto poner en práctica en breve lo que allí vi.

Del primer día cierro con la charla de Miquel Company, muy dinámica e interesante, amén de los chascarrillos que sugería el destino de tales mejoras.  Symfony, React y 7,5 Millones de usuarios diarios.

Del segundo día solo me sorprendió la charla de César Suárez sobre concurrencia usando el componente Lock. Me parece muy interesante, como técnico y sobre todo amante del sistema a bajo nivel, que se haya encapsulado todo el sistema de semáforos para acceso concurrente en un componente. Aunque no todo en el campo es orégano, también hay que tener cuidado al usar en producción soluciones basadas en este componente, sobre todo si hacemos uso de la redundancia.

Al finalizar hubo sorteos y premios por parte de Acilia relacionados con un reto que nos pusieron el primer día.

Espero poder asistir al próximo evento, preferiblemente cerca de casa, en Castellón. Mi sincero agradecimiento a los ponentes, a los organizadores y a las empresas que con su apoyo permiten que estas conferencias existan, incluida la mía.



sábado, 17 de junio de 2017

VLCSOFTING17

Valencia Softing 2107  #VLCSOFTING17

Un evento diferente y lleno de nuevas muy interesantes. En la tercera edición acudí de la mano (y gracias a) Miguel Vilata, buen amigo además de compañero.
Interesantísima las charlas de Andrés L. Martínez Ortiz   aka almo  sobre #MachineLearning, sobre todo la parte práctica de la misma con un ejemplo muy currado en base a un "invitador" automático para fiestas, en base al aspecto (o etiquetas) de las fotos de perfiles sociales de nuestros amigos o conocidos.
Igual de interesante la charla sobre funciones de Azure de la mano de  Alejandro Campos Magencio, me recordó mucho a las lambda functions de AWS.  Lástima que la supuesta característica de open source esté supeditada a tener un servidor Windows, cosa a la cual no creo que acceda nunca. En todo caso creo que invertiré algo de tiempo este verano probando esas funciones con el crédito que proporciona  la plataforma.


Con la charla de Carlos Sahuquillo Pascual sobre trolls y coches automáticos creo que toda la audiencia disfrutó de la misma manera que yo. En poco tiempo, según parece, podremos contar con coches de nivel 5, o sea, completamente autónomos. No me atreví a preguntar si los coches actuales serían algo así como los proscritos del sistema, y de alguna manera posibles causas de accidentes.

El catering fue espectacular tanto para el almuerzo, comida como merienda.
Después de la comida los tracks se dividieron y mis intereses me hicieron asistir a las charlas de Big Data, que encontré algo aburridas, no sé si por el fermento de la comida en mi estómago o por mi falta de conocimiento en el tema. En todo caso el formato de las charlas en general me gustó mucho por la capacidad de intervención de los asistentes, que formularon preguntas muy interesantes.
La clausura del evento llegó de la mano de un sorteo de varios cursos, un ipad y un fin de semana de hotel, ¡qué más se puede pedir!
Por poner algún pero, y haciendo gala al espíritu crítico que me caracteriza, las sillas del auditorio principal eran de un incómodo supino, los apoya brazos, rectos y de metal, parecen armas de castigo para que el que pasa.

En general ha sido una experiencia recomendable y repetible, espero poder  asistir el año que viene si las circunstancias me lo permiten.

miércoles, 11 de febrero de 2015

Improving the use of a MongoDB database with the help of Symfony'sListeners

Sometimes applications need to filter large amounts of information to show to the user a small subset of relevant data.

However, when the amount data to filter is too large, it may not be feasible to filter the information retrieving the whole data into memory.

Read this article to learn about an alternative approach using a MongoDB document and Symfony listeners to limit the amount of data that needs to be traversed in memory.


Continue reading in PHPClasses.org

miércoles, 4 de febrero de 2015

Cálculo de las distancias entre coordenadas GPS

En varios de los últimos proyectos, sobre todo aquellos relacionados con aplicaciones móviles, he tenido que implementar un sistema de cálculo de distancias entre coordenadas GPS.

Para evitar en el futuro la repetitiva tarea de incorporar el código fuente he creado un repositorio que me va a permitir disfrutar de esa característica con una simple línea en el archivo composer.json de cada proyecto.

// composer.json
{
   "require":{
      "jlaso/gps": "dev-master"
   }
}


La clase la tienes disponible en github.

El funcionamiento de momento es muy sencillo, pues sólo está incluida la función que calcula la distancia. La idea es ir añadiendo poco a poco más funciones que tengan como base el GPS.


He preparado también los tests oportunos que podrás ejecutar con PhpUnit desde la raíz del proyecto.

Si crees que esta clase te es útil sólo te pido que la descargues a través de la página de PHPClasses y que la votes en su momento.

Espero vuestros comentarios.

lunes, 2 de febrero de 2015

Cómo conseguir que gettext funcione en php

A menudo necesitamos internacionalizar los programas que desarrollamos en PHP.

En mi libro Programación profesional en PHP con Slim, París y Twig trato este tema en el capítulo 13. Los ejemplos de como usarlo los podemos encontrar en el código de la aplicación de ejemplo My-simple-web.

Una de las formas de internacionalizar más extendida en los desarrollos web consiste en el uso de la librería ICU, Intl y gettext.

La función gettext en PHP tiene un alias (subrrayado) que hace más compacta la lectura del código, por que permite concentrarse en el texto y no en la función.

Veámos un ejemplo:


   print _('This is a text that have to be translated');    
   print gettext('This line is equivalent to the line above.');    
?>


Para que gettext pueda saber la equivalencia entre el mensaje que queremos mostrar y el que le estamos pasando necesitamos crear un catálogo de traducciones. El programa más extendido para hacer esto se llama PoEdit.


Aunque se puede poner como texto clave la cadena normal tal y como he mostrado en los ejemplos anteriores (esto se hace por si falla la traducción tener un texto en un lenguaje digamos, natural), cada vez más se está extendiendo, sobre todo debido a los frameworks, el uso de etiquetas en las cadenas claves. Esta técnica permite categorizar las entradas del catálogo, y no simplemente tener amontonadas las claves.


Así, un ejemplo habitual siguiendo estas directrices sería:


  
   print _("general.button.label.home");    
?>    


El lector podrá apreciar enseguida que el uso de esa nomenclatura permite tener una estructura arbórea muy conveniente. Así es como funciona por ejemplo www.tradukoj.com, mi proyecto para ayudar a los desarrolladores a centralizar sus cadenas para traducir.

Una vez entendidos estos principios básicos, y teniendo entendido que queremos aprovecharnos de ellos en nuestros desarrollos, vamos a necesitar configurar nuestro intérprete de PHP para que acepte esta orden, ya que no forma parte de la SPL.

Lo habitual es que tengamos que instalar en el sistema la libreria ICU, INTL y el soporte para ambas en PHP.

Vamos a ver los diferentes casos que nos podemos encontrar en función del sistema operativo.

Windows

En este sistema operativo lo habitual es disponer de soporte para PHP mediante alguna de las aplicaciones que vienen como un todo en uno. También conocidas como WAMP (de su homólogo LAMP: Linux-Apache-Mysql-PHP), tenemos con el mismo nombre WAMP; AppServ y XAMPP.

En todas ellas la activación de una librería estándar suele ser un paso muy sencillo. Basta con marcar la librería en la configuración o irse directamente al archivo php.ini y descomentar la línea:
;extension=php_intl.dll


Concretamente, para el caso de Xampp puedes encontrar enlace directo a la edición de php.ini en el programa Panel de control de Xampp


xampp-config

Linux (Ubuntu/Centos y variantes)

Para instalar soporte de idiomas en el S.O Linux, si es que no lo tenemos ya, será suficiente con instalar INTL, ICU y el conector para PHP.



Ubuntu:

sudo apt-get install intl icu php5-intl


Centos:

yum install intl icu php5-intl


OsX

En función de si tenemos MacPorts o Brew instalaremos el soporte de idiomas de esta manera:


Mac Ports:

sudo port install php5-intl


Brew:

brew install php5-intl


En ambos casos para activar la extensión hay que editar php.ini y agregar o descomentar esta línea:

extension=intl.so


En todos los sistemas operativos podemos ver que php.ini usa nuestro sistema lanzando este comando desde la terminal:

php --ini


Ten en cuenta en todo caso que algunos sistemas usan un php.ini para el cli (interfaz de consola) y otro para el servidor web (apache normalmente).

Ahora sólo nos queda comprobar que funciona.

Para que el sistema operativo sepa que idioma estamos esperando cuando le mandamos gettext, necesitamos configurarlo antes que nada, por ello, de manera habitual, en el bootstrap de nuestra aplicación haremos algo como lo que tenemos aquí:


   @bindtextdomain('default', dirname(__FILE__).'/');    
   @textdomain ('default');    
   $langs = array (    
    'es' => 'ES',    
    'en' => 'GB',    
   );    
   $code = isset($_REQUEST['lang'])?$_REQUEST['lang']:'es';    
   if (isset($langs[$code]))    
     $iso_code = $code.'_'.$langs[$code];    
   else{    
     $code = "es";    
     $iso_code = 'es_ES';    
   }    
   if (isset($_SESSION['lang'])) $_SESSION['lang']=$code;    
   putenv ('LANGUAGE='.$iso_code);    
   putenv ("LC_ALL=$iso_code");    
   setlocale(LC_ALL, $iso_code);    
?>



Recuerda: si te da algún error como este:

Fatal error: Call to undefined function _() in ... on line x

Vuelve a repasar la configuración.


En todo caso, si se te resiste el uso de esta librería en local y en producción te funciona correctamente, no es nada descabellado utilizar este truco:


if(!function_exists('_')){
    function _($key){ return $key; }
}

Esto no te traducirá los textos, pero al menos no te romperá el código.

Espero vuestros comentarios.

lunes, 26 de enero de 2015

Animación estilo metro con Titanium

En ocasiones he necesitado indicar al usuario de la aplicación móvil que un proceso necesita cierto tiempo para realizarse, como la conexión a un servidor para enviar o recoger datos, o sencillamente actualizar la posición GPS.
A menudo, siguiendo las tendencias actuales, esta información se requiere de una forma limpia y no intrusiva.
Siguiendo estos parámetros he creado una utilidad que realiza una animación sobre el borde superior de una vista, utilizando un gradiente en el color de fondo (backgroundColorGradient).

La función animateGradient está incluida dentro del módulo utils.js, por tanto para usarla haremos algo así:

var utils = require('/utils');
var activity = utils.animateGradient(view,options);
// una vez terminada la actividad invocaremos
activity.destroy();


view es un contenedor de tipo Titanium.UI.createView y options puede albegar de momento las opciones que indican la velocidad de animación y los colores.

Veamos como está hecho.


animateGradient: function (view, options)
{
// in android isn't possible to include a view inside an object that is not a view
if((Titanium.Platform.osname == "android") && (view.toString() !== '[object View]')){
alert('Error on type calling animateGradient, called with '+view.toString());
setTimeout(function(){
Titanium.Android.currentActivity.finish();
},2500);
return;
}

...
}


Lo primero es comprobar que se ha llamado con el tipo de parámetro adecuado, pues en android no se puede usar un contenedor que no sea un objeto de tipo Titanium.UI.createView, en iOS he probado con un button directamente y funciona bien.


animateGradient: function (view, options)
{
// check if view parameter is right in android platform ...
if(typeof(options) === "undefined"){
options = {};
}
var timeout = options.timeout || 75;
var colors = options.colors || ['#0000aa', '#000099'];

...
}


Ahora se establecen las opciones por defecto para el caso de no indicarlas, luego explicaré para que hacen falta dos colores.


animateGradient: function (view, options)
{
// check if view parameter is right in android platform ...
// establish default options and set it to local variables ...
/**
* the view that host the activity bar
*/
var linearGradient = Titanium.UI.createView({
top: 0,
left: 0,
width: '100%',
height: '3dp',
zIndex: 1000
};
view.add(linearGradient);

...
}


Lo primero es crear una vista que va a albergar nuestra animación, como puedes ver estará en el borde superior, con ancho total y una altura de 3dp, el valor de zIndex es como medida de precaución para que sea visible en cualquier caso.


animateGradient: function (view, options)
{
// check if view parameter is right in android platform ...
// establish default options and set it to local variables ...
// create animation view container inside the main view ...
var gradient = {
inc: 0.02,
x: 0.05,
colorIndex: 0,
colors: colors,
baseColor: view.backgroundColor,
color: function(){
this.colorIndex = (this.colorIndex+1)%2;
return this.colors[this.colorIndex];
}
};

...
}


Creamos un objeto auxiliar para encapsular toda las variables que necesitamos. Fíjate como el color de fondo es exactamente el mismo que la vista principal.


animateGradient: function (view, options)
{
// check if view parameter is right in android platform ...
// establish default options and set it to local variables ...
// create animation view container inside the main view ...
// auxiliary object in order to encapsulate all variables ...
/**
* timer that simulates the activity though changing gradient color
*/
var interval = setInterval(function(){
if((gradient.x > 0.95) || (gradient.x<0.05)){
gradient.inc = -gradient.inc;
}
gradient.x += gradient.inc;
linearGradient.backgroundGradient = {
type: 'linear',
startPoint: { x: '0%', y: '50%' },
endPoint: { x: '100%', y: '50%' },
colors: [
{ color: gradient.baseColor, offset: 0.0},
{ color: gradient.baseColor, offset: gradient.x-0.05},
{ color: gradient.color(), offset: gradient.x },
{ color: gradient.baseColor, offset: gradient.x+0.05 },
{ color: gradient.baseColor, offset: 1.0 }
]
};
}, timeout);

...
}


Ahora viene la parte que realmente hace la animación, un intervalo que en cada iteración crea la ilusión de que el gradiente del color de fondo se mueve. Para forzar a Titanium a que pinte la vista de nuevo hay que indicar cada vez un color diferente, por eso se utilizan dos colores, para que el efecto sea más bonito y auténtico no deben distar mucho ambos colores, fíjate en los que se usan por defecto.


animateGradient: function (view, options)
{
// check if view parameter is right in android platform ...
// establish default options and set it to local variables ...
// create animation view container inside the main view ...
// auxiliary object in order to encapsulate all variables ...
// timer that simulates the activity though changing gradient color ...
return {
linearGradient: linearGradient,
view: view,
destroy: function(){
clearInterval(this.interval);
view.remove(linearGradient);
},
interval: interval
};
}


Al final se devuelve un objeto con los datos necesarios, el más importante es la función que nos va a permitir deshacernos de la vista temporal usada para crear la animación.



El código fuente completo de la animación incluido en una pequeña aplicación de ejemplo lo tienes a tu disposición en mi cuenta de github.

Vamos a ver el ejemplo de uso completo:


...
var testBtnView = Titanium.UI.createView({
backgroundColor: "#aaa",
top: "50%",
height: '50dp',
width: "225dp"
});
win.add(testBtnView);

var testBtn = Titanium.UI.createButton({
color: 'white',
backgroundColor: '#aaa',
title: "TEST",
font: {fontSize:'18dp',fontFamily:'Helvetica Neue',fontWeight:'bold'},
textAlign: 'center'
});
testBtnView.add(testBtn);
...
function testBtnHandler(){
console.info("test button clicked");
activity = utils.animateGradient(testBtnView);
// simulate an action that lasts 5 seconds
setTimeout(function(){
activity.destroy();
}, 5000);
}
...


Espero vuestros comentarios.