Weblogs Código

Poesía Binaria

Un monitor VGA que a veces no quiere funcionar enchufado a un puerto HDMI… con script chapuza

abril 24, 2017 08:42

Todo me sucedió hace algo menos de un año cuando compré un nuevo portátil de la sexta generación de Intel. Estos portátiles ya no traen puerto VGA. Sencillamente los Skylake de Intel no los soportan y no hay mucho que podamos hacer. Y, si nuestro portátil trae ese puerto, o bien la placa base tiene un adaptador integrado (aunque a estas alturas no sé si habrá algún fabricante que meta un puerto así en un portátil de última generación), o no es un Skylake (de ahí en adelante. Tampoco será un Kaby Lake).

Al mismo tiempo, ya le tenía cariño a mi monitor externo. Un Samsung 4:3 con el que llevaba ya 10 años (ya pronto cumplirá 11) y que sigue funcionando como el primer día a pesar de llevar a sus espaldas varias mudanzas, algún que otro golpe y muchísimas horas de vuelo.

Adaptadores HDMI – VGA

Lo primero fue adquirir Adaptador HDMI a VGA como el que muestro en la foto a continuación. Lo más fácil es buscar a partir del enlace anterior o mirar en un chino. También lo puedes encontrar con salida de audio. Suelen rondar los 7€ sin audio y lo 9-10€ con audio.

Aunque, esto puede ser una solución ideal y muchas veces no es mala solución. Por ejemplo si tienes un Raspberry Pi y lo quieres usar con tu viejo monitor no te queda otra. Y, aunque no lo he probado, dicen por ahí que se portan bien con este tipo de adaptadores. No suelen calentarse mucho, a mí me dio la impresión al principio, hasta que noté que estaba justo delante de la salida de aire del portátil.

El problema

Este tipo de adaptadores suele tener problemas a la hora de comunicar la información del monitor al ordenador. Es decir, toda la información EDID que envía el monitor para identificarse, decir qué resolución soporta, y demás. Y eso provoca que muchas veces enchufemos el monitor y no funcione. Aunque cuando lo tengas un rato enchufado sí que funcione. A veces, la información EDID llega y otras veces no llega. Puede depender del conversor, aunque si lo compras en un chino, la devolución puede llegar a ser odiosa. De todas formas, mirando por Internet, no fui el único que le pasaba esto.

Otro problema es el refresco de la pantalla (cuando funcionaba la pantalla), puede que tras enchufar el conversor, notéis un cierto desfase en la pantalla. Por ejemplo, al reproducir un vídeo, veáis una línea horizontal o varias dividiendo el vídeo. O incluso cuando ponéis una pantalla de terminal o de bloc de notas pase un tiempo perceptible desde que pulsáis la tecla hasta que veis la letra en pantalla. Y no es cosa del ordenador, porque, en el caso del portátil, pude poner la misma ventana en el panel e iba mucho más rápido. Incluso intentar que la misma letra se representara en las dos pantallas a la vez y en la pantalla externa tardaba un poco más.

El problema del refresco

Para solucionar el problema del refresco sólo jugué con los valores de refresco de la pantalla, sincronización vertical y horizontal. Hasta que encontré un punto satisfactorio y que funcionaba siempre. Es cierto que en ocasiones me daba algún bote la pantalla con el driver nvidia, pero cuando pasé del kernel 4.4 a la versión 4.8 todo empezó a ir mucho más fluido.

El problema de la detección

Aunque lo que más me preocupaba es que no me detectaba el monitor a la primera. Para mí no era mucho problema porque apenas apago el ordenador. Lo tengo funcionando todo el día, a todas horas, pero aunque sea una vez al mes lo reinicio. Y es que cuando lo reiniciaba podía estar desde un par de minutos hasta varias horas sin poder utilizar el monitor externo. Por un lado, el problema era del driver de NVIDIA. Al ser un portátil con doble tarjeta gráfica, podía comprobar que con el driver de Intel el monitor lo detectaba mucho antes (a veces no, pero eran las menos). Aunque por otro lado, también era culpa del adaptador, porque cuando enchufo un monitor HDMI en el ordenador todas las ventanas (incluso la configuración de la BIOS) se podían ver por el monitor (cosa que no pasaba cuando tenía el adaptador puesto).

Así que investigando cómo hacerlo podíamos encontrar información útil. Todos los ejemplos los pondré utilizando xrandr porque para mí era mucho más cómodo que hacerlo en xorg.conf

Lo primero será cambiar el proveedor de salida, es decir, quién se encarga de gestionar la salida de vídeo. Podemos obtener una lista de proveedores de:

xrandr --listproviders

Y luego seleccionar el proveedor que deseemos. Este es mi caso, pero tal vez en vuestro sistema sea diferente:

xrandr --setprovideroutputsource 2 0

Tenemos que tener cuidado con esto, ya que si introducimos un valor ilegal en versiones antiguas de Xorg (me pasó con una Ubuntu 16.04), se puede quedar el ordenador congelado. En versiones más nuevas, ya nos avisa del problema y no hace cambios en el sistema.

Por otro lado, debemos insertar el modo de pantalla a mano. Ya que el problema es al comunicar la resolución soportada del monitor al ordenador. El ordenador no la coge bien y no representa nada. Así que digámosle la información del monitor a mano. En mi caso, la resolución era 1280×1024 a 60Hz y, aunque mi ordenador sólo tiene un puerto HDMI, el nombre con el que el sistema lo detecta es HDMI-1-2 (el nuevo driver de NVIDIA tiene nombres muy largos). Además, quiero la pantalla a la derecha de la del panel del portátil. Para eso hacemos lo siguiente:

xrandr --newmode “1280x1024_60.00” 108.88  1280 1360 1496 1712  1024 1025 1028 1060  -HSync +Vsync
xrandr --addmode HDMI-1-2 1280x1024_60.00
xrandr --output HDMI-1-2 --mode 1280x1024_60.00 --right-of eDP-1-1

Y ya tenemos que tener el monitor funcionando.

Kde Plasma 5

Con la llegada de KDESC 5, llega el nuevo escritorio Plasma y un montón de innovaciones y cosas chulas que podemos hacer. Pero la gestión de la pantalla, cuando creamos un modo de vídeo a mano nos empieza a dar problemas. El modo de vídeo se crea, y podemos representar cosas en la pantalla, pero no tenemos fondo de escritorio y algunos efectos se verán un poco raros, porque para KDE es como si no tuviéramos pantalla. Es más, si accedemos a la configuración nos dice disconnected, aunque la pantalla está claramente enchufada y hay imágenes en pantalla. Esto con un monitor HDMI directamente no pasa..
El problema aquí es que KDE le pregunta a su proveedor, xrandr si hay monitor, y como el monitor no manda bien la información (algunos programas existentes para leer la información EDID devuelven error en el checksum), el proveedor devuelve un error y el monitor figurará como desconectado. Dado que algunas veces sí que se envía bien la información y por lo tanto, el monitor aparecerá como conectado, dando el aspecto que tiene que tener la imagen, vamos a intentar pedir la información al monitor hasta que nos la devuelva bien. Con cuidado, porque, aunque nos la devuelva bien, si volvemos a preguntar, tal vez la entregue mal de nuevo y nos toque volver a empezar.

Para ello, tenemos este script:

while [ -n “$(xrandr | grep ‘HDMI-1-2.*disconnected’)” ]; do sleep 1; done

Con esto, estaremos llamando a xrandr, que refrescará la información del monitor, y mirará si junto con el nombre de la salida, HDMI-1-2, figura también el mensaje disconnected. Si es así, espera un segundo y vuelve a intentar. Así, puede que en unos minutos tengamos el monitor detectado. Otra solución es dejarlo enchufado, a veces lo solía coger solo, aunque podía tardar horas.

El caso en Windows

Pero claro, muchos pensaréis que es cosa de GNU/Linux. El caso es que Windows (probé Windows 10) sí que terminaba detectando el monitor y más o menos pronto. Como mucho entre uno y cuatro minutos. El problema, es que tras enchufar el monitor externo con el conversor, el sistema quedaba congelado, y las pantallas parpadeando durante todo ese tiempo, aunque al final el monitor funcionaba sin tener que hacer scripts ni nada raro.

Foto: Caspar Rubin

The post Un monitor VGA que a veces no quiere funcionar enchufado a un puerto HDMI… con script chapuza appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 279

abril 24, 2017 06:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET/.NET Core


ASP.NET/ASP.NET Core

Azure / Cloud

Conceptos/Patrones/Buenas prácticas

Data

Html/Css/Javascript

Visual Studio/Complementos/Herramientas

Otros

Componentes/bibliotecas

  • Xabaril
    Toogle Features and A/B testing library for ASP.NET Core
Publicado en Variable not found

» Leer más, comentarios, etc...

PHP Senior

Tips: borrar paquetes viejos de PHP 7.0 si estás migrando a PHP 7.1 (Ubuntu)

abril 22, 2017 06:31



Puede pasar que estás instalando paquetes y te diste cuenta que hay cosas que no están funcionando correctamente. Por ejemplo, si estás instalando PHP 7.1, puede ser que te queden paquetes anteriores de PHP 7.0 que no desinstalaste, por lo que este comando te puede ayudar a solucionarlo:

 sudo apt-get purge php7.0-common

Reading package lists... Done
Building dependency tree    
Reading state information... Done
The following packages will be REMOVED:
  libapache2-mod-php7.0* php7.0-cli* php7.0-common* php7.0-curl* php7.0-gd* php7.0-json* php7.0-mbstring* php7.0-mysql* php7.0-opcache* php7.0-readline*
  php7.0-sqlite3* php7.0-xml*
0 upgraded, 0 newly installed, 12 to remove and 0 not upgraded.
After this operation, 16,7 MB disk space will be freed.
Do you want to continue? [Y/n] Y

UPDATE: encontré un buen resumen en https://ayesh.me/Ubuntu-PHP-7.1

» Leer más, comentarios, etc...

PHP Senior

PHP 7.1: cómo instalarlo en Ubuntu

abril 22, 2017 06:29

Para quienes quieren probar PHP 7.1 en Ubuntu, ya que actualmente no está disponible como paquete oficial de instalación, pueden seguir los siguientes pasos:


sudo add-apt-repository ppa:ondrej/php

sudo apt-get update

sudo apt-get upgrade

(optional) sudo apt-get remove php7.0

sudo apt-get install php7.1


Anexo (otros paquetes que puedes necesitar)


sudo apt-get install php7.1-mbstring


sudo apt-get install php7.1-xml

Instalación probada en Ubuntu 16.04 / 17.04 LTS sin problemas.

Saludos! 

» Leer más, comentarios, etc...

Blog Bitix

Información sensible en los contenedores con Docker Secrets

abril 22, 2017 10:00

Parte de la información que usan los contenedores de Docker se debe proteger de accesos no deseados. Anteriormente en algunos casos se usaban variables de entorno para lanzar los contenedores lo que no es seguro si se listan los procesos del sistema con sus parámetros, incluir archivos en las imágenes de los contenedores tampoco es recomendable. Docker Secrets permite proporcionar y mantener segura la información sensible que usen los contenedores.

Docker

Los contenedores de Docker necesitan acceder a algunos datos sensibles desde el punto de vista de la seguridad como usuarios y contraseñas, certificados SSL, claves privadas SSH o cualquier otra información de acceso restringido. Algunos de estos datos en Docker se proporcionan mediante variables de entorno al lanzar los contenedores, esto es inseguro ya que al hacer un listado de los procesos con sus parámetros de invocación los relativos a Docker mostrarán esta información, lo que es un posible problema de seguridad.

Con Docker Secrets se puede gestionar esta información que se necesita en tiempo de ejecución pero que no se quiere almacenar en la imagen de Docker o en el repositorio de código fuente. Algunos ejemplos de información sensible son:

  • Nombres de usuario y contraseñas.
  • Certificados TLS y claves.
  • Claves SSH.
  • Otra información sensible como el nombre de una base de datos o el nombre de un servidor interno.

Los secretos de Docker se proporcionan a los contenedores que los necesitan y se transmiten de forma cifrada al nodo en el que se ejecuten. Los secretos se montan en el sistema de archivos en la ruta /run/secrets/<secret_name> de forma descifrada al que el servicio del contenedor puede acceder.

Algunos comandos para manejar los secretos son los siguientes:

  • docker secret create secreto: crea un secreto.
  • docker secret inspect secreto: muestra los detalles de un secreto.
  • docker secret ls: lista los secretos creados.
  • docker secret rm secreto: elimina un secreto.
  • Se usa el parámetro –secret para docker service create y –secret-add y –secret-rm flags para docker service update.

Usando un stack de servicios con un archivo de Docker Compose en la sección secrets de los servicios se indica cuales usa, en la sección secrets se definen los secretos de los servicios con sus nombres y su contenido referenciando archivos que pueden ser binarios o de text no superior a 500 KiB.

Al servicio de nginx la clave privada y certificado para configurar el acceso mediante el protocolo seguro HTTPS se le proporciona a través de secretos que son referenciados en el archivo de configuración del servidor web nginx.conf.

Por otra parte la aplicación Java con Spring Boot lista el contenido de los secretos incorporados en el contenedor cuando se solicita en la URL https://192.168.99.100/system/info/, esto no se debe hacer porque se pierde la seguridad que proporcionan los secretos pero sirve a modo de muestra en el ejemplo.

Contenido del archivo message.txt

Para probar el ejemplo hay que ejecutar varios comandos, la secuencia completa es la siguiente:

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

xailer.info

Novedades de Xailer 5 (V)

abril 21, 2017 12:37

Estimados usuarios,

En este nuevo artículo quiero presentaros los nuevos controles que he realizado para mejorar la edición desde Xailer y adaptarlos a Windows 10 y su uso con tabletas. El control original de Windows adolece de varios problemas, que son:

  • Imposibilidad de centrar su texto verticalmente en su marco contenedor, lo cual obligaba a cambiar el font según el alto del control para adaptarlo más o menos.
  • Imposibilidad de cambiar el estilo ‘contraseña’ una vez creado el control
  • Imposibilidad de cambiar completamente su marco

Además, en Windows 10 podemos observar como existen nuevas funcionalidades en algunos controles de edición, como:

  • Un texto de ayuda cuando el control está vació
  • Una imagen de una lupa para disparar una búsqueda
  • Una imagen para borrar el contenido del control (muy útil con tabletas)
  • Una imagen para mostrar el contenido de un control de edición tipo contraseña

Todas estas nuevas funcionalidades se han incorporado en dos nuevos controles de nombre TEditMod y TMemoMod, que por supuesto tienen sus equivalentes como DataControls TDbEditMod y TDbMemoMod. Os dejo una imagen de los mismos:

TEdiMod y TMemoMod

TEdiMod y TMemoMod


El pintado de las distintas imágenes que muestra el control es completamente vectorial utilizando técnicas de ‘antialiasing’ que incorpora GDI+.

Espero que los nuevos controles sean de vuestro agrado. ¡Hasta muy pronto!

Un cordial saludo

» Leer más, comentarios, etc...

Picando Código

System76 – fabricante de computadoras con Ubuntu apunta a fabricar también el hardware

abril 20, 2017 06:30

System76 es una empresa fabricante de computadoras especializada en notebooks, computadoras de escritorio y servidores. Tienen la particularidad que soportan al software libre y sus computadoras ofrecen Ubuntu como única opción de sistema operativo instalado.

System76

En su oferta de laptops cuentan con modelos livianos como el Lemur de 14 pulgadas y la Galaga Pro de 13, así como máquinas de gama alta (sacrificando portabilidad) como la Serval WS de 15 y 17 y el Bonobo WS de 17. En escritorio la oferta arranca con Meerkat, una computadora de 10 cm de tamaño pero bastante potente. También ofrecen equipos de gama alta terminando con la monstruosa Silverback WS.

El número 76 en el nombre alude al año de la Revolución de Estados Unidos: 1776. Los fundadores esperan de la misma manera comenzar una revolución open source, llevando a una situación en que los consumidores no necesiten software privativo. La compañía promueve activamente a Ubuntu, enviando stickers, patrocinando el Ubuntu Developer Summit, y apoyando reuniones y grupos de usuarios locales.

En un post titulado “Entering Phase Three” en su blog, describen un poco las fases por las que fue pasando la empresa, hasta llegar a lo que esperan cambiar actualmente en su modelo de fabricación:

Fase uno – tomar cerveza y hablar sobre código abierto. Armar una empresa haciendo computadoras con Linux. Ver si a alguien le interesa. He aquí, ¡la gente quiere computadoras con Linux! Mucho esfuerzo. Ocho años después, ¡perseverar!

Fase dos – ¿Quiénes somos? Tenemos principios. ¿Nuestra marca retrata nuestros principios? No muy bien. Corrijamos eso. Re-marca y definirnos claramente. “Hacemos computadoras potentes para ayudarte a hacer más, ir más lejos, y liberar tu potencial”.

Hasta ese punto la empresa tomaba todas las decisiones del stack de software desde el firmware hasta Ubuntu, entregando hardware de última generación y asegurándose que funcione con los lanzamientos de Ubuntu. Lo que no puede hacer es diseñar y fabricar sus productos, lo hace a través del outsourcing. Y esto cambiaría a partir de la próxima fase:

Fase tres – Mover el diseño y fabricación del producto a la empresa. Esto empezaría con las computadoras Model S. Los principios de diseño, al ser System76, son el polo opuesto del resto de la industria:

  • Representar el carácter de la empresa – Una empresa abierta, cálida, amigable, de alta calidad. Sus diseños reflejan estas características.
  • Representar a la comunidad Open Source – Su trabajo CAD será de código abierto y su diseño rendirá tributo a la ciencia de la computación.
  • Fácil de trabajar y expandir – El producto será flexible, para abrirlo, cambiarlo y expandirlo.
  • Eficiente de fabricar – En este aspecto mencionan robots y automatización, eficiencia para mantener costos competitivos. Al igual que el desarrollo de software, integrarán mejoras en el diseño del producto y la producción contínuamente.

Esta nueva etapa comenzará con las computadoras de escritorio. Tanto el diseño como el CAD están encaminados, y están prototipando con acrílico y pasando a metal pronto. Las primeras computadoras diseñadas y fabricadas en la empresa estarán listas el año que viene. Las laptops son más complejas y seguirán más adelante.

Si bien las System76 se entregan con Ubuntu, sabemos que cuentan con la compatibilidad de hardware en Linux, por lo que cambiar a nuestra distribución preferida no debería traer problemas. Por mi parte me gustaría ir teniendo una de esas Meerkat con Debian como Media Center en mi tele…

Es una apuesta arriesgada pero esperemos que la empresa tenga éxito. Que mantengan planes así de ambiciosos después de varios años de producción es una buena señal. Necesitamos más empresas que ofrezcan una solución integral de computadoras con Linux, y productos que respeten la libertad del usuario y su poder sobre los dispositivos que compra.

» Leer más, comentarios, etc...

Poesía Binaria

¿Cómo visualizar la progresión del código fuente de un proyecto en vídeo?

abril 20, 2017 10:50

Nos gusta desarrollar, y muchas veces, sobre todo cuando el equipo de desarrollo es grande, apetece enseñar la evolución de nuestro código a los demás de una forma visual. Y, ¿qué mejor que con un vídeo? Un vídeo nos ayudará, de un vistazo a ver lo grande que es nuestro proyecto, las épocas de más o menos cambios, los usuarios implicados, etc. Y luego puedes colgar el vídeo en alguna red social de vídeos, utilizarlo como apoyo para presentaciones, y mucho más. Por cierto, no sólo está limitado a nuestros proyectos, cualquier código al que podamos acceder por git, svn, csv, etc será susceptible de ser convertido en vídeo.

Visualizar código con gource

Gource es un programa que hace una representación de los repositorios en el tiempo. Analiza el contenido de los logs y crea una muestra en pantalla con movimientos suaves y efectos de luces utilizando OpenGL. El software se puede ejecutar tanto en Linux como Windows y es muy estable, lo he probado en repositorios de varios gigas y casi 10 años de commits sin problema. Es verdad que si hacemos un vídeo, sobre todo por la resolución y la conversión de vídeo va un poco más lento, pero es algo que puede quedarse en segundo plano.

Para los ejemplos, he utilizado el repositorio de GitHub del proyecto openshot-qt. Porque me ha parecido interesante hacerlo con este proyecto.

Combinación con ffmpeg/avconv

Gource, es incapaz de exportar a vídeo, pero como en la mayoría de aplicaciones que ejecutamos en GNU/Linux, podemos encadenar comandos y pasar el flujo de datos de una aplicación a otra, por ejemplo, una aplicación como ffmpeg o avconv con la que podemos hacer pasar streams de imágenes y guardarlas en formato vídeo con el formato que queramos… o incluso le podemos añadir música o efectos si pasamos el vídeo a los programas adecuados.

En principio, vamos a guardar nuestro vídeo generado con gource en un archivo de vídeo, y para ello utilizaremos la salida en formato de secuencia de imágenes PPM que nos da gource y lo encadenaremos con una tubería hasta avconv, con el que crearemos el vídeo definitivo:

gource -1920×1080 openshot-qt/ --stop-at-end -s 1 -c 1.6 --camera-mode track --multi-sampling -r 25 --user-scale 3 --highlight-users --disable-progress -a 1 -o --  | avconv -y -r 25 -f image2pipe -c:v ppm -i pipe:0 -threads 1 -c:v libx264 -preset ultrafast -f mp4 videofinal.mp4

Expliquemos esto un poco:
  • -1920×1080 : Indica la resolución. Vamos a hacer un vídeo en Full HD.
  • openshot-qt/ : Es el directorio del repositorio, debemos indicar un directorio que tenga algún tipo de control de versiones: git, svn, csv…
  • –stop-at-end : El programa finalizará cuando termine de analizar commits. En realidad este argumento no es necesario, porque -o implica que se pare al finalizar, pero nos viene bien por si queremos practicar un poco con gource antes de crear el vídeo.
  • -s 1 : Nos dará la velocidad de la simulación en segundos por día. Aunque en realidad el número de segundos que tiene un día en el repositorio puede variar dependiendo del número de commits que haya ese día.
  • -c 1.6 : Acelera la simulación un poco. 1 sería mantenerla a la misma velocidad. 4 sería el máximo.
  • –camera-mode track : los modos válidos son overview y track. Probad los dos y quedaos con el que más os guste.
  • –multi-sampling : reduce el aliasing de un modo económico.
  • -r 25 : mi vídeo tendrá 25 fotogramas por segundo.
  • –highlight-all-users : resalta los nombres de todos los usuarios. Podemos, si queremos resaltar un usuario concreto con –highlight-user [nombre_de_usuario]
  • –disable-progress : Elimina la barra de progreso en la ventana de gource. No haría falta, puesto que -o anula esta configuración. Pero, como antes, si queremos investigar nos viene bien.
  • -a 1 : Si no hay commits durante un segundo, saltamos al siguiente commit en el tiempo.
  • -o – : Exportamos el vídeo generador en formato de secuencia de imágenes PPM. Aquí podemos dar un nombre de archivo base o, como en este caso – indicará que las imágenes se exportarán en la salida del programa. Si lo ejecutamos tal cual, se empezará a llenar la terminal de símbolos raros. Es mejor poner – sólo cuando vayamos a encadenar con otro comando.

Ahora bien, hemos terminado de analizar los argumentos para gource, ahora la salida de este programa la vamos a pasar a la entrada de avconv (esto lo hacemos con |, que indicará una tubería o pipe). Analicemos los argumentos de avconv:

  • -y : No pide confirmación para sobreescribir los archivos. Es útil cuando estamos haciendo pruebas, pero por seguridad podríamos quitarlo.
  • -r 25 : El número de fotogramas por segundo, 25. Lo especificamos en gource, pero lo volvemos a indicar aquí para que los dos programas trabajen a la misma velocidad. Podríamos jugar con estos parámetros para conseguir más o menos velocidad.
  • -f image2pipe : El formato de los datos de entrada será en forma de secuencia de imaǵenes a través de pipe (la tubería que hemos hecho encadenando los dos programas).
  • -c:v ppm El formato de los datos de la secuencia de imágenes es PPM. (Para que gource y avconv hablen el mismo idioma).
  • -i pipe:0 : Las imágenes de entrada vendrán por la entrada estándar (0) a través de pipe.
  • -threads 1 : Utilizaremos sólo 1 thread para la compresión. Podremos utilizar más, o no especificar este parámetro para que sea automático y tal vez vaya todo un poco más rápido. Pero si el repositorio es muy grande, y vamos a necesitar varias horas para crear el vídeo, tal vez nos interese poder utilizar el ordenador para otras cosas mientras, así que limito el número de cores a utilizar.
  • -c:v libx264 : Voy a utilizar un format de vídeo h264 (mp4). Puedo especificar si quiero con -b:v los bits por segundo para mi vídeo, para aumentar o disminuir la calidad.
  • -preset ultrafast : libx264 utiliza preconfiguraciones con parámetros para facilitarnos la creación de vídeo, en este caso van determinados por velocidad. Cuanto más lenta se haga la compresión, más pequeño será el archivo, ultrafast, en este caso nos proporciona la compresión más rápida, pero el tamaño de archivo más grande (h264 comprime mucho, pero si ponemos un veryslow aquí veremos una calidad similar y un archivo mucho más pequeño).
  • -f mp4 : Crearemos un archivo mp4. Aquí podremos poner avi, o matroska si lo preferimos. En realidad no hace falta puesto que la extensión del archivo (siguiente parámetro lo especifica, pero está bien para investigar.
  • videofinal.mp4 : nombre de archivo donde vamos a guardarlo.

Ahora, sólo tenemos que dejar que el programa haga lo que tiene que hacer, si el repositorio es muy grande tardará un rato bastante largo. Es importante no cerrar la ventana de gource mientras se está creando el vídeo. Además, podemos manejarla de forma interactiva, haciendo zoom, moviendo o utilizando teclas especiales para mostrar más o menos información.

Añadiendo una pista de música

Un vídeo así queda muy soso, así que vamos a utilizar avconv para añadir una pista de música. Yo he utilizado una pista de Josh Woodward. Un artista que tiene una gran colección de canciones Creative Commons tanto instrumentales como con voz. Así que, le vamos a decir a avconv que utilice una pista de audio de la siguiente forma:

gource -1920×1080 openshot-qt/ --stop-at-end -s 1 -c 1.6 --camera-mode track --multi-sampling -r 25 --user-scale 3 --highlight-all-users --disable-progress -a 1 -o --  | avconv -y -r 25 -f image2pipe -c:v ppm -i pipe:0 -i ~/Descargas/JoshWoodward-NQC-NoVox-03-SheDreamsInBlue.mp3 -threads 1 -c:v libx264 -preset ultrafast -f mp4 videofinal.mp4

Como vemos, es muy parecido a lo de antes, sólo añadiendo una pista más con -i

Vídeo generado

Añadiendo efectos

Pero claro, esto está muy bien, pero en GNU/Linux no estamos limitados a esto. Podríamos crear, directamente, el vídeo añadiendo algún efecto. Avconv dispone de sus filtros, aunque vamos a utilizar filtros de MLT, muchos de estos filtros también se procesan en GPU y utilizan multi-threading, por lo que, aunque a avconv le digamos que utilice solo un thread, tal vez MLT esté haciendo un uso más intensivo de CPU para aplicar sus filtros.
Aunque en futuros posts me dedicaré más a fondo a este software, aquí haremos una pequeña introducción.

Para empezar, la música, en lugar de introducirla con avconv, la voy a introducir con MLT (en adelante, en línea de comandos, será melt). El hecho de hacerlo con melt, es porque melt va a utilizar datos provenientes de una pipe de avconv (es un jaleo, gource pasa datos por pipe a avconv y avconv otra pipe para melt). Mi objetivo era simplificar la pipe para melt y no complicarme la vida exportando el audio también.

Aquí va el pequeño comando:


$ melt -audio-track ~/Descargas/JoshWoodward-NQC-NoVox-03-SheDreamsInBlue.mp3 -track pipe:1 que sería una pipe de salida estándar. Por otro lado, le hemos dicho que el bitrate de vídeo sea de 12000k por segundo.

Luego a melt le hemos dicho:

  • -audio-track [pista.mp3] : Especificamos una pista de audio. ¡La música de fondo!
  • -track
  • -filter sepia : Especificamos un filtro sepia. Podemos probar con muchísimos fitlros. Ejecutad $ melt -query filters para verlos todos
  • -consumer avformat:videofinal.mp4 : Grabamos el vídeo en el archivo videofinal.mp4

Con melt también podemos añadir efectos de audio, fundidos, marca de agua e infinidad de cosas más como veremos en futuros posts.

Sugerencias y comentarios

Deja un comentario debajo con tus dudas, sugerencias, ideas y frikadas. Y si tienes un repositorio del que hayas hecho un vídeo, déjamelo por aquí, seguro que es muy interesante verlo.

The post ¿Cómo visualizar la progresión del código fuente de un proyecto en vídeo? appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Navegapolis

Decisiones estratégicas

abril 19, 2017 09:57

Vas al comité:
—Entonces la decisión es esta.
—¿Pero esa decisión?
—La ha tomado Franken & Goursen.
Cuantos más apellidos tenga la consultora, más chula es la decisión; más millones inviertes.
—¡Franken & Goursen & Young!, ¡joder, pues hay que tomarla!
Luego si la decisión sale bien, te pones una medalla. La decisión es un análisis, un "benchmark"; se tiene que llamar "benchmark" no se puede llamar análisis, o... (ni me sale la palabra en español de benchmark) bueno, un contraste, ¡o como sea!
Entonces, si la decisión sale bien, te pones una medalla. La empresa se pone una medalla porque ha hecho lo que todo el mundo sabía. Si la decisión sale mal:
—¡Pero tío, han sido los consultores!
Y vas a los consultores, a los "Fransen & Goursen & Young":
—No, has implementado mal, porque el mercado ha hecho esto y ha funcionado.
Es la cobardía de que no puedes tomar una decisión que sea diferente o que sea natural por el mero hecho de que te van a poner en la calle.
Entonces si quitas todo eso la gente vuelve a descubrir un mundo normal:
—¿Y esta decisión?.
—Pues porque yo creo que es la correcta.

De la mesa redonda de Agile Spain 2014 - Pedro Serrahima - Director General Grupo Globalia.

» Leer más, comentarios, etc...

Adrianistán

Novedades de C++17

abril 19, 2017 06:51

Después de tres años de trabajo, C++17 ha sido finalmente estandarizado. Esta nueva versión de C++ incorpora y elimina elementos del lenguaje, con el fin de ponerlo al día y convertirlo en un lenguaje moderno y eficaz. El comité ISO de C++ se ha tomado muy en serio su labor, C++11 supuso este cambio de mentalidad, que se ha mantenido en C++14 y ahora en C++17, la última versión de C++.

Repasemos las noveades que incorpora C++17 respecto a C++14

if-init

Ahora podemos incluir una sentencia de inicialización antes de la condición en sentencias if y switch. Esto es particularmente útil si queremos operar con un objeto y desconocemos su validez.

// ANTES
Device dev = get_device();
if(dev.isOk()){
    dev.hacerCosas();
}

// AHORA
if(Device dev = get_device(); dev.isOk()){
    dev.hacerCosas();
}

Declaraciones de descomposición

Azúcar sintántico que permite mejorar la legibiliad en ciertas situaciones. Por ejemplo, en el caso de las tuplas, su uso se vuelve trivial.

// FUNCIÓN QUE DEVUELVE TUPLA
std::tuple<int, std::string> funcion();

// C++14
auto tup = funcion();
int i = std::get<0>(tup);
std::string s = std::get<1>(tup);

// C++17
auto [i,s] = funcion();

Esto funciona para multitud de estructuras de datos, como estructuras, arrays, std::array, std::map,…

std::map m = ...;
for(auto && [key, value] : m){

}

Deduction Guides

Ahora es menos necesario que nunca indicar los tipos en ciertas expresiones. Por ejemplo, al crear pares y tuplas:

// ANTES
auto p = std::pair<int,std::string>(42,"Adrianistan");

// AHORA
auto p = std::pair(42,"Adrianistan");

Esto por supuesto también sirve para estructuras y otras construcciones:

template<typename T>
struct Thingy
{
  T t;
};

// Observa
Thingy(const char *) -> Thingy<std::string>;

Thingy thing{"A String"}; // thing.t es de tipo std::string

template auto

// ANTES
template <typename T, T v>
struct integral_constant
{
   static constexpr T value = v;
};
integral_constant<int, 2048>::value
integral_constant<char, 'a'>::value

// AHORA
template <auto v>
struct integral_constant
{
   static constexpr auto value = v;
};
integral_constant<2048>::value
integral_constant<'a'>::value

Fold expressions

Imagina que quieres hacer una función suma, que admita un número ilimitado de parámetros. En C++17 no se necesita apenas código.

template <typename... Args>
auto sum(Args&&... args) {
   return (args + ... + 0);
}

Namespaces anidados

Bastante autoexplicativo

// ANTES

namespace A{
    namespace B {
        bool check();
    }
}

// AHORA

namespace A::B {
    bool check();
}

Algunos [[atributos]] nuevos

[[maybe_unused]]

Se usa para suprimir la advertencia del compilador de que no estamos usando una determinada variable.

int x = 5;
[[maybe_unused]] bool azar = true;
x = x + 10

[[fallthrough]]

Permite usar los switch en cascada sin advertencias del compilador.

switch (device.status())
{
case sleep:
   device.wake();
   [[fallthrough]];
case ready:
   device.run();
   break;
case bad:
   handle_error();
   break;
}

Variables inline

Ahora es posible definir variables en múltiples sitios con el mismo nombre y que compartan una misma instancia. Es recomendable definirlas en un fichero de cabecera para luego reutilizarlas en ficheros fuente.

// ANTES
// en una cabecera para que la usasen los demás
extern int x;

// solo en un fichero fuente, para inicializarla
int x = 42;
// AHORA

// en la cabecera
inline int x = 42;

if constexpr

Ahora es posible introducir condicionales en tiempo de compilación (similar a las macros #IFDEF pero mejor hecho). Estas expresiones con constexpr, lo que quiere decir que son código C++ que se evalúa en tiempo de compilación, no de ejecución.

template<class T>
void f (T x)
{
    if  constexpr(std:: is_integral <T>::value)  {
        implA(x);
    }
    else  if  constexpr(std:: floating_point <T>::value)  {
        implB(x);
    }
    else
    {
        implC(x);
    }
}

std::optional

Tomado de la programación funcional, se incorpora el tipo optional, que representa un valor que puede existir o no. Este tipo ya existe en Rust bajo el nombre de Option y en Haskell como Maybe.

std::optional opt = f();
if(opt)
    g(*opt);

// otra opción de uso si queremos proveer de un reemplazo
std::optional opt = f();
std::cout << opt.value_or(0) << std::endl;

std::variant

Descritas como las unions pero bien hechas. Pueden contener variables de los tipos que nosotros indiquemos.

std::variant<int, double, std::vector> precio; // precio puede ser un int, un double o un std::vector

// comprobar si el valor en un variant es de un determinado tipo
if(std::holds_alternative<double>(precio))
    double x = std::get<double>(precio);

std::any

Si con std::variant restringimos los posibles tipos de la variable a los indicados, con std::any admitimos cualquier cosa.

std::any v = ...;
if (v.type() == typeid(int)) {
   int i = any_cast<int>(v);
}

std::filesystem

Se añade a la librería estándar este namespace con el tipo path y métodos para iterar y operar con directorios. Dile adiós a las funciones POSIX o Win32 equivalentes.

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void main(){
  fs::path dir = "/";
  dir /= "sandbox";
  fs::path p = dir / "foobar.txt";
  std::cout << p.filename() << "\n";
  fs::copy(dir, "/copy", fs::copy_options::recursive);
}

Algoritmos en paralelo

Muchos de los algoritmos de STL ahora pueden ejecutarse en paralelo bajo demanda. Con std::execution::par indicamos que queremos que el algoritmo se ejecute en paralelo.

std::sort(std::execution::par, first, last);

¿Qué novedades se esperan en C++20?

Ya hemos visto lo que trae C++17. Ahora veremos que se espera que traiga C++20 en 2020.

  • Módulos. Reemplazar el sistema de includes
  • Corrutinas. Mejorar la programación asíncrona
  • Contratos. Mejorar la calidad del código
  • Conceptos. Mejorar la programación genérica
  • Redes. Estandarizar la parte de red en C++ tal y como se ha hecho con std::filesystem
  • Rangos. Nuevos contenedores

Referencias:

 

La entrada Novedades de C++17 aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Picando Código

StarCraft original es ahora gratuito

abril 19, 2017 12:45

Con motivo del próximo lanzamiento de StarCraft: Remastered, Blizzard publicó para descargar e instalar StarCraft clásico totalmente gratis. Esto incluye StarCraft y su expansión StarCraft: Brood War.

StarCraft

En 2008 comentaba cómo parchear una nueva versión de StarCraft en Wine. Seguramente jugué mucho más tiempo StarCraft en Wine que con mi vieja laptop con Windows 2000. En esos momentos nos preocupábamos por poder jugar sin tener que andar con el CD del juego arriba, hoy mi laptop no tiene lectora de CD’s. También hacía un comentario interesante sobre los juegos en Linux:

(…) que comiencen a migrar sus juegos a GNU/Linux!! Una compañía grande, que una sola de las compañías grandes dé el primer paso, el resto no se va a quedar afuera…

Gracias Valve, larga vida a Gabe Newell.

Con la versión el parche 1.18, se hacen gratis ambos juegos y se ponen a disposición versiones para Mac y Windows. También hay varias correcciones más que pueden ver en detalle en la publicación oficial.

Si bien versiones anteriores de StarCraft para Windows funcionaban casi perfectamente con Wine, no es el caso de esta versión gratuita. La instalación sí funciona, y el juego queda instalado en nuestro directorio de Wine:

Instalando StarCraft

Pero un error no permite la ejecución del juego una vez instalado. Por suerte el bug en cuestión ya fue reportado en la AppDB de Wine, así que supongo que será cuestión de tiempo que una versión más nueva de Wine implemente los cambios para que el juego funcione.

En los comentarios del bug hay usuarios que confirman haber logrado hacer funcionar el juego usando wine-overwatch. Sin embargo, después de compilar e instalar esta versión de Wine no funcionó para mí. Con un poco más de paciencia posiblemente se logre.

Estaría genial una versión nativa de StarCraft para Linux, pero dudo que pase. Por lo pronto a esperar esa nueva versión de Wine que arregle el asunto, que los juegos de Blizzard han funcionado bastante bien así en el pasado.

» Leer más, comentarios, etc...

Picando Código

Firefox simplifica sus canales de lanzamiento: No más Aurora

abril 18, 2017 03:00

Firefox Aurora Nightly

A partir del 18 de Abril, el canal de Firefox Aurora dejará de ser actualizado. Hasta ahora, Aurora era la versión de Firefox donde se probaban nuevas funcionalidades para ser integradas en la versión estable. Era un paso intermedio entre Nightly y Beta.

Eventualmente el build de Aurora será eliminado del ciclo de lanzamientos. La versión Developer Edition estará basada en el build Beta. Aquellos que vengan usando la Developer Edition mantendrán sus temas, herramientas y preferencias, manteniendo sus perfiles existentes y no deberían experimentar nada raro.

Con este cambio, los canales de pre-lanzamiento quedan más claros:

Nightly – Canal experimental, para probar características nuevas a diario, cambios en el código todos los días.

Developer Edition/Beta – Canal estable, con características cerca de ser integradas a la versión estable. A partir de la desaparición de Aurora, calidad y estabilidad mayor.

El cambio también ayuda a que los ciclos de lanzamiento sean más ágiles.

A partir del 18 de abril, el código de Firefox 54 se mueve de Aurora a Beta, como sucede usualmente, mientras que Firefox 55 se mantendrá en Nightly para un segundo ciclo seguido (un total de 14 semanas). Para el próximo día de merge, 12 de junio, Firefox 55 se moverá directamente de Nightly a Beta. Entre Abril y Junio, Firefox Aurora para Escritorio (54) seguirá recibiendo actualizaciones para problemas críticos de seguridad y las poblaciones de Aurora y Developer Edition serán migradas al canal de actualización Beta. En Android, los usuarios de Aurora serán migrados a Nightly.

Aurora fue creado originalmente en 2011 para proveer más feedback de los usuarios cuando Firefox se movió de la versión 5 a un ciclo de lanzamiento de alta velocidad. ¿Se acuerdan cuando celebrábamos las salidas de nuevas versiones de Firefox con records mundiales? Ahora salen demasiado seguido… En 2017 Mozilla considera que cuenta con procesos más modernos en su modelo, y cree que puede entregar productos llenos de características y estables sin las 6 a 8 semanas que sumaba la fase Aurora.

El mecanismo de lanzamiento de cada versión continuará teniendo chequeos adicionales para asegurar versiones de alta calidad. Las nuevas características pasarán de Nightly a Beta únicamente cuando se las considere listas, basado en criterios pre-establecidos determinados por los equipos de ingeniería, producto e integridad de producto. Si algo no está listo, no pasará de Nightly a Beta.

Pueden leer más información en el blog de Release Management de Mozilla.

Personalmente vengo usando Nightly como navegador por defecto desde hace mucho tiempo y se nota una mejora gigante en la estabilidad respecto a tiempos anteriores. De todas formas, como dice el post enlazado más arriba, se está trabajando en mejorar la estabilidad general de esta versión. Existen nuevas condiciones para que una característica entre a los builds de Nighlty, se integrarán analizadores estáticos para identificar potenciales problemas, se analizará cobertura de código y más. Se vienen buenos tiempos para los usuarios de Nightly y Firefox en general 🙂

Más info:
Simplifying Firefox Release Channels and Improving Developer Edition’s Stability

 

» Leer más, comentarios, etc...

Picando Código

GNU IceCat 52 – Nueva versión del navegador web orientado a la libertad y privacidad

abril 18, 2017 02:40

¿Se acuerdan de GNU IceCat? Es la versión GNU del navegador web Firefox, orientada a proteger la libertad y privacidad de sus usuarios.
GNU IceCat

El código está basado en la versión oficial de Firefox ESR (versión de soporte extendido), quitando el arte registrado y los componentes privativos. No se trata simplemente de un fork de Firefox ESR, es un esfuerzo en paralelo que trabaja cerca y en sincronía con Firefox, elevando parches cuando es posible. Notar que algunas actualizaciones de seguridad adicionales son personalizadas para IceCat ocasionalmente.

Con un foco en la privacidad del usuario, incluye algunas funcionalidades extra. Está diseñado para que resulte fácil bloquear la ejecución de JavaScript no-libre con GNU LibreJS o deshabilitar todo el JS, bloquear rastreadores de privacidad con SpyBlock (fork de Adblock Plus), redirigir a HTTPS cuando es posible con HTTPS Everywhere. También cuenta con contramedidas para fingerprinting, una serie de técnicas que permiten identificar singularmente a un navegador basado en características específicas de esa instancia en particular (como qué fuentes están disponibles en el sistema). A diferencia de las cookies el usuario no puede optar por no ser rastreado de esta manera, así que el navegador tiene que evitar dar esa información.

Por último, a diferencia de nuevas versiones de Firefox que brindan soporte para DRM (Gestión de restricciones digitales) mediante su implementación de EME (Encrypted Media Extension), GNU IceCat no lo incluye ya que se opone completamente a la tecnología DRM.

Recientemente se lanzó la versión 52 de GNU IceCat. Hubo un gran cambio ya que la versión estable que se venía manteniendo era la 45 (basado en la versión de Firefox con ese número de versión). Entre los cambios notables, se incluyen:

  • Detección y manejo de portales cautivos
  • Soporte nativo para FLAC
  • Muchas mejoras de rendimiento y seguridad
  • Ventanas multi proceso (Electrolysis). Esto está deshabilitado por defecto ya que no es compatible con LibreJS.

Pueden descargar GNU IceCat y leer más sobre el navegador en el sitio web de GNU.

La versión móvil del navegador está siendo considerado para ser incluida en F-Droid, el catálogo de aplicaciones software libre para dispositivos Android.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 278

abril 18, 2017 07:02

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET/.NET Core

ASP.NET/ASP.NET Core

Azure / Cloud

Conceptos/Patrones/Buenas prácticas

Html/Css/Javascript/Web

Visual Studio/Complementos/Herramientas

Cross-platform

Otros

Componentes/bibliotecas

Y para terminar, un vídeo donde seguro os reconocéis porque… ¿quién no ha parcheado una aplicación directamente en producción? ;D



Publicado en Variable not found

» Leer más, comentarios, etc...

Poesía Binaria

Cómo obtener información de salud de tu sistema Linux o tu servidor en C y C++ (I. Memoria)

abril 17, 2017 08:46

Es algo necesario para el buen funcionamiento de nuestras aplicaciones. Para añadir robustez a nuestros sistemas, haciendo que éstos se comporten de manera adecuada en situaciones imprevistas. Por ejemplo, si necesitamos crear un archivo y añadir cierta información, podemos, a priori, comprobar que el dispositivo en el que vamos a escribir tiene suficiente espacio libre, lo que nos puede ahorrar suplicios si los ficheros son demasiado grandes. También deberíamos ser capaces de comprobar que un disco de red está conectado antes de trabajar con él y, en muchos casos, anticiparnos a largas esperas y bloqueos por parte del sistema operativo. O incluso tomar una decisión ante una tarea intensiva que no es prioritaria comprobando primero cómo de ocupado está nuestro sistema.

La serie de posts sobre salud del sistema

El post me ha salido enorme por lo que he decidido dividirlo en varias partes que se publicarán una cada dos semanas. Como siempre, voy intercalando posts sobre otras temáticas relacionadas, para no estar siempre hablando de lo mismo.
Si sois impacientes y queréis ver mucha información y resultados con código, podéis ver este proyecto en GitHub (linuxmon) donde se tratan muchos de estos temas. Aunque, en esta serie de posts me gustaría completar algo más la información obtenida y, estará todo explicado.

C y C++ en los ejemplos

Muchos ejemplos son básicamente acceso a ficheros o utilización de funciones de C de bibliotecas de Linux y POSIX. Aunque he utilizado C++ para facilitar un poco la salida de datos y pelearme un poco menos con la memoria en algunos casos utilizando string, vector, map y alguna cosa más. De todas formas, para aclaraciones sobre cómo hacer todo esto en C puro, podéis preguntar en los comentarios.

Para el código C++, he utilizado algunas de las novedades disponibles a partir de la especificación de 2011 que, como ya tiene 6 años, considero que todos podremos compilarlo.

Por tanto, para compilar estos ejemplos, debemos hacer:

g++ -o ejecutable fuente.cpp -std=c++11 -lpthread

La biblioteca pthread la necesito sólo para un par de ejemplos en los que utilizo hilos y alguna que otra guarrada de la que no me siento orgulloso, pero que a lo mejor puede resultar de ayuda para alguien.

Algunas funciones que nos serán de ayuda

Está claro que como programadores, no es difícil pelearnos nos bytes, por ejemplo para comprobar que el espacio libre es mayor a un tamaño determinado. Lo malo es que imprimir en pantalla números más o menos grandes (del orden de miles de millones) para expresar tamaños puede resultar difícil de leer, así que utilizaremos funciones como size() para obtener el tamaño en formato humano.

De forma parecida, con el objetivo de simplificar nuestro código, la función extractFile() leerá un archivo en su totalidad y lo almacenará en un buffer.
Ambas funciones las pongo aquí junto con un ejemplo de uso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include <unistd.h>                         /* Utilidades UNIX */
#include <fcntl.h>                          /* Control de archivos */

/** Humanize size */
std::string size(long double size, int8_t precision=3)
{
    static const char* units[10]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"};
    char temp[32];
    char format[10];

    int i= 0;

    while (size>1024)
        {
            size = size /1024;
            i++;
        }

    snprintf(format, 10, "%%.%dLf%%s", precision);
    snprintf(temp, 32, format, size, units[i]);

    return std::string(temp);
}

/** Extract a whole file into a string  */
std::string extractFile(const char *filename, size_t bufferSize=512)
{
    int fd = open(filename, O_RDONLY);
    std::string output;

    if (fd==-1)
        return "";      /* error opening */

    char *buffer = (char*)malloc(bufferSize);
    if (buffer==NULL)
        return "";      /* Can't allocate memory */

    int datalength;
    while ((datalength = read(fd, buffer, bufferSize)) > 0)
        output.append(buffer, datalength);

    close(fd);
    return output;
}

using namespace std;

int main(int argc, char* argv[])
{
    cout << size(1023339229) << endl;
    cout << extractFile("utils.cpp")<<endl;
    return 0;
}

En ambas funciones podría haber utilizado utilidades de C++ para la lectura de ficheros, o para transformar los números en cadena de caracteres y presentar la información. En este caso no lo hice así por cuestión de velocidad, ya que pienso utilizar estas funciones muchas veces y necesito que se ejecuten bien en sistemas pequeños. En mis pruebas, incluso con la optimización de compilador, he conseguido que estas funciones se ejecuten entre un 40% y un 60% más rápido. Y es normal, las bibliotecas de streams de C++ son muy complejas, no tendríamos limitación por tamaño de cadenas, por ejemplo, para el caso de size(). Y en el caso de extractFile(), utilizo directamente las funciones de unistd. Están más cerca del sistema operativo y se comportan más rápido que las de C y mucho más que las de C++.

Pongo aquí una versión en C puro de la función size() que también utilizaré en algunos ejemplos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char* size(char* storage, long double size, int8_t precision)
{
    static const char* units[10]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"};
    char format[10];

    int i= 0;

    while (size>1024)
        {
            size = size /1024;
            i++;
        }

    snprintf(format, 10, "%%.%dLf%%s", precision);
    snprintf(storage, 32, format, size, units[i]);

    return storage;
}

Información de memoria y uso del sistema: sysinfo()

En Linux, tenemos la función sysinfo() dentro de sys/sysinfo.h, que devuelve en una estructura homómina (struct sysinfo) la siguiente información:

  • long uptime : Segundos desde el arranque del ordenador.
  • unsigned long loads[3] : Carga media en 1, 5 y 15 minutos (que podemos ver con el comando uptime). La carga del sistema está expresada en la medida que diga la constante SI_LOAD_SHIFT, es decir, el 100% de carga podría ser por ejemplo 65536 por lo que para encontrar el % real debemos dividir por ese número.
  • unsigned long totalram : Memoria RAM total
  • unsigned long freeram : Memoria RAM libre
  • unsigned long sharedram : Memoria RAM compartida
  • unsigned long bufferram : Memoria RAM usada en buffers
  • unsigned long totalswap : Memoria SWAP total
  • unsigned long freeswap : Memoria SWAP libre
  • unsigned short procs : Número de procesos en marcha actualmente. Aunque en este caso indica los hilos en ejecución, que serán muchos más.
  • unsigned int mem_unit : Tamaño de la unidad de memoria en bytes

Como ejemplo podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <sys/sysinfo.h>
#include <time.h>
#include <stdint.h>

#include <string.h>                         /* strerror() */
#include <errno.h>
#include <stdlib.h>

/** Humanize size */
char* size(char* storage, long double size, int8_t precision)
{
    static const char* units[10]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"};
    char format[10];

    int i= 0;

    while (size>1024)
        {
            size = size /1024;
            i++;
        }

    snprintf(format, 10, "%%.%dLf%%s", precision);
    snprintf(storage, 32, format, size, units[i]);

    return storage;
}

void panic(char* error)
{
    fprintf (stderr, "Error: %s (%d - %s)\n", error, errno, strerror(errno));
    exit(-1);
}

int main(int argc, char* argv[])
{
    struct sysinfo si;
    struct tm temptime;     /* Utilizado para presentar uptime en formato humano */
    char temp[32];              /* Cadena temporal para los tamaños. (función size()) */
   
    if (sysinfo(&si) <0)
        panic("No puedo hacer sysinfo");

    localtime_r(&si.uptime, &temptime);

    printf ("Uptime: %ld (%d años, %d meses %d dias %d horas %d minutos %d segundos)\n",
                    si.uptime,
                    temptime.tm_year-70,    /* Los años empiezan en 1970, restamos 70*/
                    temptime.tm_mon,
                    temptime.tm_mday-1,     /* El primer día es el 1 */
                    temptime.tm_hour,
                    temptime.tm_min,
                    temptime.tm_sec);

    printf ("Carga del sistema: %lu %lu %lu (%f %f %f)\n",
                    si.loads[0], si.loads[1], si.loads[2],
                    si.loads[0] / (float) (1<<SI_LOAD_SHIFT),
                    si.loads[1] / (float) (1<<SI_LOAD_SHIFT),
                    si.loads[2] / (float) (1<<SI_LOAD_SHIFT));
    printf ("Procesos corriendo: %d\n", si.procs);
    printf ("Unidad de memoria: %d (%s)\n", si.mem_unit, size(temp, si.mem_unit, 3 ));
    printf ("RAM total: %ld (%s)\n", si.totalram, size(temp, si.totalram*si.mem_unit, 3));
    printf ("RAM free: %ld (%s)\n", si.freeram, size(temp, si.freeram*si.mem_unit, 3));
    printf ("RAM compartida: %ld (%s)\n", si.sharedram, size(temp, si.sharedram*si.mem_unit, 3));
    printf ("RAM buffers: %ld (%s)\n", si.bufferram, size(temp, si.bufferram*si.mem_unit, 3));
    printf ("RAM usada: %ld (%s)\n", si.totalram - si.freeram - si.bufferram,
                    size(temp, (si.totalram - si.freeram - si.bufferram)*si.mem_unit, 3));
    printf ("SWAP total: %ld (%s)\n", si.totalswap, size(temp, si.totalswap*si.mem_unit, 3));
    printf ("SWAP free: %ld (%s)\n", si.freeswap, size(temp, si.freeswap*si.mem_unit, 3));
    printf ("SWAP usada: %ld (%s)\n", si.totalswap - si.freeswap, size(temp, (si.totalswap-si.freeswap)*si.mem_unit, 3));
    printf ("Total high: %ld (%s)\n", si.totalhigh, size(temp, si.totalhigh*si.mem_unit, 3));
    printf ("Free high: %ld (%s)\n", si.freehigh, size(temp, si.freehigh*si.mem_unit, 3));
   
    return 0;
}

En el ejemplo anterior, lo que puede que no quede muy claro es la memoria alta total y libre. Este tipo de memoria se utiliza en sistemas que tienen que dividir la memoria principal para poder acceder a ella correctamente. Como ejemplo, podemos indicar dispositivos con un ancho de bus inferior a la cantidad de memoria a la que pueden acceder, hace unos años era muy común esto, por ejemplo en CPUs de 16bit que necesitaban poder acceder a varios megabytes de RAM (cuando con 16bit no podríamos expresar números mayores de 65535). O, hace algunos años menos, cuando CPUs de 32bit con arquitectura Intel debían acceder a más de 4Gb de RAM, lo que se conocía como PAE o Physical Address Extension. La memoria alta sería aquella a la que no podíamos acceder tan directamente.

Información sobre paginado de memoria

Existen tres funciones más para averiguar:

  • Tamaño de páginas de memoria. getpagesize() o sysconf(_SC_PAGESIZE) como veremos más adelante.
  • Total de páginas en RAM (páginas físicas). get_phys_pages(). Este número de páginas será la cantidad total de memoria entre el tamaño de página.
  • Total de páginas físicas disponibles. get_avphys_pages(). Que debe ser la cantidad de memoria libre entre el tamaño de página.

Vemos un pequeño ejemplo aquí:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
#include <sys/sysinfo.h>

int main(int argc, char* argv[])
{
    printf ("Tamaño de página: %d\n", getpagesize());
    printf ("Páginas físicas: %ld\n", get_phys_pages());
    printf ("Páginas físicas disponibles: %ld\n", get_avphys_pages());

    return 0;
}

Más información sobre la memoria: /proc/meminfo

Con el comando anterior vemos mucha información sobre la memoria aunque, no toda la información que nos da Linux. Si hemos curioseado proc veremos que existe el archivo /proc/meminfo con mucha información más. Eso sí, tenemos que parsear el fichero para extraer la información. Afortunadamente no es un fichero físico, sino un vínculo en nuestro sistema de archivos que se actualiza en tiempo real. Y podemos acceder a él como si fuera un archivo, y eso es muy bueno, muy filosofía unix y nos facilita el acceso a la información desde cualquier lenguaje. Aunque ahora vamos a hacerlo en C++ para facilitarnos un poco la vida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <sstream>
#include <string>
#include <unistd.h>                         /* Utilidades UNIX */
#include <fcntl.h>                          /* Control de archivos */

/** Humanize size */
std::string size(long double size, int8_t precision=3)
{
    static const char* units[10]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"};
    /* Using a char instead of ostringstream precission because it's faster */
    char temp[32];
    char format[10];

    int i= 0;

    while (size>1024)
        {
            size = size /1024;
            i++;
        }

    snprintf(format, 10, "%%.%dLf%%s", precision);
    snprintf(temp, 32, format, size, units[i]);

    return std::string(temp);
}

/** Extract a whole file into a string  */
std::string extractFile(const char *filename, size_t bufferSize=512)
{
    int fd = open(filename, O_RDONLY);
    std::string output;

    if (fd==-1)
        return "";      /* error opening */

    char *buffer = (char*)malloc(bufferSize);
    if (buffer==NULL)
        return "";      /* Can't allocate memory */

    int datalength;
    while ((datalength = read(fd, buffer, bufferSize)) > 0)
        output.append(buffer, datalength);

    close(fd);
    return output;
}

using namespace std;

int main(int argc, char* argv[])
{
    string memInfo = extractFile ("/proc/meminfo");
    if (memInfo.empty())
        {
            cerr << "Error al leer información"<< endl;
            terminate();
        }

    stringstream ss (memInfo);
    string unit;
    string name;
    unsigned long num;
    ss >> name >> num >> unit;
    while (ss.good())
        {
            name.erase(name.size()-1, 1);
            cout << name<< " -> " << size(num*1024, 3)<<"\n";
            ss >> name >> num;
            ss.ignore(10, '\n');            /* Eliminamos la unidad o hasta el salto de línea */
        }

    return 0;
}

Además, si queremos, podemos crear una función que almacene todo en un mapa de C++ con lo que podremos acceder muy fácilmente a cada uno de los elementos leidos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <unistd.h>                         /* Utilidades UNIX */
#include <fcntl.h>                          /* Control de archivos */

/** Humanize size */
std::string size(long double size, int8_t precision=3)
{
    static const char* units[10]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"};
    /* Using a char instead of ostringstream precission because it's faster */
    char temp[32];
    char format[10];

    int i= 0;

    while (size>1024)
        {
            size = size /1024;
            i++;
        }

    snprintf(format, 10, "%%.%dLf%%s", precision);
    snprintf(temp, 32, format, size, units[i]);

    return std::string(temp);
}

/** Extract a whole file into a string  */
std::string extractFile(const char *filename, size_t bufferSize=512)
{
    int fd = open(filename, O_RDONLY);
    std::string output;

    if (fd==-1)
        return "";      /* error opening */

    char *buffer = (char*)malloc(bufferSize);
    if (buffer==NULL)
        return "";      /* Can't allocate memory */

    int datalength;
    while ((datalength = read(fd, buffer, bufferSize)) > 0)
        output.append(buffer, datalength);

    close(fd);
    return output;
}

std::map<std::string, unsigned long> linuxMemory()
{
    std::map<std::string, unsigned long> out;
   
    std::string memInfo = extractFile ("/proc/meminfo");
    if (memInfo.empty())
        {
            std::cerr << "Error al leer información\n";
            std::terminate();
        }

    std::stringstream ss (memInfo);
    std::string unit;
    std::string name;
    unsigned long num;
    ss >> name >> num >> unit;
    while (ss.good())
        {
            name.erase(name.size()-1, 1);
            out[name] = num;
            ss >> name >> num;
            ss.ignore(10, '\n');
        }
    return out;
}

using namespace std;

int main(int argc, char* argv[])
{
    for (auto d : linuxMemory())
        {
            std::cout << "["<<d.first << "] : "<<d.second<<"\n";
        }
    return 0;
}

Memoria utilizada por nuestro programa

Aunque trataremos el tema en profundidad en un futuro post, podemos averiguar la memoria total del programa, tamaño del código, datos y pila leyendo el fichero /proc/self/statm, también podríamos leer el fichero /proc/self/stat (que analizaremos más adelante) o incluso /proc/self/status que deberemos parsear como /proc/meminfo . Por ahora podemos quedarnos con lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>                         /* Utilidades UNIX */
#include <fcntl.h>                          /* Control de archivos */

/** Extract a whole file into a string  */
std::string extractFile(const char *filename, size_t bufferSize=512)
{
    int fd = open(filename, O_RDONLY);
    std::string output;

    if (fd==-1)
        return "";      /* error opening */

    char *buffer = (char*)malloc(bufferSize);
    if (buffer==NULL)
        return "";      /* Can't allocate memory */

    int datalength;
    while ((datalength = read(fd, buffer, bufferSize)) > 0)
        output.append(buffer, datalength);

    close(fd);
    return output;
}

using namespace std;

int main(int argc, char* argv[])
{
    std::string memInfo = extractFile ("/proc/self/statm");

    if (memInfo.empty())
        {
            std::cerr << "Error al leer información\n";
            std::terminate();
        }
    unsigned long size,
        resident,
        share,
        text,
        lib,
        data,
        dt;
   
    std::stringstream ss (memInfo);
   
    ss >> size >> resident >> share >> text >> lib >> data >> dt;

    cout << "Tamaño total: "<<size<<"\n";
    cout << "Tamaño residente: "<<resident<<"\n";
    cout << "Páginas compartidas: "<<share<<"\n";
    cout << "Tamaño de código: "<<text<<"\n";
    cout << "No usado: "<<lib<<"\n";
    cout << "Datos + Pila: "<<data<<"\n";
    cout << "No usado: "<<dt<<"\n";
   
    return 0;
}

Los tamaños vienen expresados en páginas, por lo que para extraer la información en bytes debemos multiplicar el valor obtenido por el tamaño de las páginas (el valor que nos devuelve getpagesize()). En Linux 2.6 o superior vemos que los valores de lib y de dt valen siempre 0, pero el sistema sigue utilizándolos para garantizar la retrocompatibilidad con programas más antiguos. Si queremos hacer este parseo del archivo en C puro podemos utilizar sscanf() que también nos dará muy buen resultado.

Siguiente post

Hablaremos de la CPU, definiciones del sistema, como el tamaño de página y cosas así que pueden resultar interesantes y utilización de recursos por parte de nuestras aplicaciones. Todo esto, a partir del 1 de mayo.

Foto principal: William Stitt

The post Cómo obtener información de salud de tu sistema Linux o tu servidor en C y C++ (I. Memoria) appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Koalite

Cómo testear el frontend de una aplicación web

abril 17, 2017 05:06

Parece que hemos decidido que la mejor forma de hacer aplicaciones es utilizando HTML y Javascript, incluso si se trata de aplicaciones de escritorio que consumen cientos de megas de RAM para realizar las tareas más básicas. Sin entrar a discutir la adecuación de tecnología a caso de uso, lo que sí está claro es que cuanto más compleja sea la parte cliente de nuestras aplicaciones web, más importante es diseñar una estrategia de testing adecuada que nos permita mantener una calidad razonable para poder dormir tranquilos.

Cuando decides ponerte a testear este tipo de aplicaciones es normal que surjan un montón de dudas. Siempre están las clásicas consideraciones sobre qué tipo de tests utilizar en cada caso, pero es que además nos encontramos con la dificultad de elegir entre decenas de librerías de testing y de escoger sobre qué plataforma o plataformas vamos a ejecutar los tests.

En este posts vamos a intentar obtener una visión global que nos permita saber qué terreno pisamos. No habrá código y ni siquiera profundizaremos en ninguna librería, pero debería servir cómo base para empezar a diseñar tu propia estrategía de testing en función de tus necesidades.

Muchas plataformas diferentes

Una de las cosas en las que más se diferencia testear un cliente web de otro tipo de aplicaciones, es en la diversidad de plataformas que tenemos para poder ejecutar los tests.

En la parte de backend, lo normal es que ni siquiera te plantees en qué plataforma vas a ejecutar los tests. Si estás trabajando con .NET, ejecutarás los tests en el framework sobre el que vas a desplegar tu aplicación, y poco más (otro caso es si se trata de una librería, en cuyo caso puede que te animes a testearla en varias versiones del framework).

Sin embargo, cuando queremos testear código Javascript, tenemos varias opciones.

OJO: En Javascript hay ocasiones en las que se diferencia entre el runner que ejecuta los tests, la librería con la que se escriben los tests, e incluso la librería que se utiliza para los asserts. No te sorprendas si tienes que juntar varias piezas para que todo funcione como te gustaría.

Podemos testear nuestro código ejecutándolo en node.js, por ejemplo utilizando mocha.js o jasmine. Si estamos escribiendo tests que no requieren acceso al DOM ni a APIs del navegador, es una alternativa muy buena porque son tests fáciles de montar y rápidos de ejecutar.

Como contrapartida, sólo estaremos testeando el comportamiento sobre V8 (el motor de javascript usado por node.js y Chrome, entre otros), con lo que teóricamente podríamos encontrar diferencias de comportamiento con respecto a otros motores de javascript. En general si no estamos tocando DOM ni APIs del navegador esto no suele ser un problema, pero hay algunas cosas concretas (como por ejemplo el parsing de fechas) que sí son dependientes del navegador y podrían llegar a darnos alguna sorpresa.

Teóricamente podríamos usar esta técnica para escribir tests que tocaran DOM o APIs del navegadores utilizando los polyfills adecuados (por ejemplo, jsdom, pero aquí sí que empezamos a pisar un terreno algo más resbaladizo y es más fácil que encontremos pequeñas diferencias entre navegadores. Pese a ello, mezcladas con librerías como enzyme, una herramienta específica para ReactJS, permiten cubrir algunos escenarios interesantes.

Otra alternativa es ejecutar los tests en un navegador de verdad. De hecho, si vas por esta vía probablemente deberías ejecutarlos en varios navegadores de verdad. Para ello puedes utilizar librerías de testing como las que indicaba antes (mocha, jasmine) y apoyarte en algún lanzador tipo karma que te permita ejecutar fácilmente los test en los navegadores.

La ventaja de estos tests, como te puedes imaginar, es que son mucho más fieles a la realidad y puedes comprobar si ese código tan bonito que has escrito funciona en Internet Explorer (probablemente no) y cuántos polyfills necesitas para hacerlo funcionar en Safari.

Montarlos es un poco más complicado y ejecutarlos es bastante más lento porque hay que arrancar el navegador y, aunque los dejes en modo watch, siguen tardando un poco más que los tests en node.js.

Dentro de este estilo, a veces es cómodo utilizar un phantom.js, un navegador basado en webkit que no tiene interfaz de usuario y que se puede controlar completamente a través de código, lo que permite ejecutar fácilmente tests en él.

Existe otra forma de ejecutar los tests en un navegador: interactuando directamente con el interfaz de usuario. La diferencia es que antes ejercitábamos nuestro código invocando funciones concretas (más en la línea de tests unitarios), y ahora tocamos botones, pulsamos enlaces e introducimos textos en inputs.

Para estos tests de integración la herramienta más popular es Selenium, que en javascript podemos utilizar a través de wrappers como protractor si usamos AngularJS o, de forma más general, nightwatch.

Este tipo de tests es el más lento de todos, pero también es el que nos permite probar con mayor fidelidad el interfaz de usuario, por lo que a veces merece la pena utilizarlo.

Una posible estrategia de testing

Una vez vistas las opciones que tenemos para ejecutar tests y sus características, sólo nos queda decidir cuáles utilizar. En esto, como siempre, el contexto es fundamental y no hay dos aplicaciones iguales, pero sí hay una serie de factores que nos pueden servir de guía.

Cuanto más fácil sea escribir los tests, mejor. Y cuanto más rápido se ejecuten, también mejor. Eso nos lleva a que deberíamos intentar aislar al máximo posible nuestra lógica en tests que podamos ejecutar sobre node.js. Para ello podemos valernos de las técnicas habituales para eliminar dependencias de los tests y aislarlos al máximo, ya no sólo del navegador, sino también de la librería o framework que estemos utilizando.

En los casos en los que necesitamos interactuar con el DOM o APIs del navegador, podríamos recurrir a utilizar lanzadores tipo karma, pero a menos que estés desarrollando una librería de componentes gráficos reutilizables, es probable que te lo puedas ahorras.

Normalmente es fácil cubrir “la miga” de la aplicación con tests unitarios de node.js, y dejar el resto para tests de integración usando Selenium y un entorno lo más parecido posible a producción.

De todas formas, como decía antes, esto es muy dependiente de la aplicación y se aplican las ideas generales sobre tipos de tests.

Testeando lenguajes transpilados a Javascript

Teniendo en cuenta la cantidad de lenguajes que se compilan a Javascript y la adopción cada vez mayor que van teniendo, es cada vez más frecuente que la aplicación que vamos a testear no esté escrita directamente en Javascript, sino en TypeScript, Kotlin, F# o PureScript (por citar unos cuantos).

En estos casos tenemos básicamente dos alternativas: integrar la parte de testing y compilación, o compilar primero a Javascript y luego testear como si fuese una aplicación Javascript normal.

Integrando la parte de compilación dentro del proceso de testing tendremos una experiencia más cómoda y ágil, sobre todo si estamos escribiendo tests unitarios para ejecutar en node.js. Además, podremos escribir los tests en el mismo lenguaje que el código y usar librerías idiomáticas para el lenguaje y podremos los errores y stacktraces referidos al código original, no al código transpilado (que en algunos casos puede ser extremadamente diferente).

La única pega es que puede ser un poco más complicado montar el proceso de compilación+testing, pero hoy en día casi todos los lenguajes tienen previsto este escenario.

Donde sí puede tener más sentido primero compilar y luego ejecutar los tests contra el código Javascript es en los tests de integración con Selenium. Puede que el lenguaje que hayamos elegido no tenga runners tan maduros como los de Javascript y además es una forma de asegurarnos de que estamos testeando contra un entorno lo más parecido a producción posible.

Conclusión

Testear el frontend de una aplicación web puede parece una tarea complicada, porque se une la dificultad propia de establecer una estrategia de testing adecuada, con la variedad de librerías existentes para testearlas, y el añadido de tener distintas plataformas para ejecutar los tests.

No obstante, una vez que se tienen claras unas bases mínimas, es perfectamente viable montar un sistema de testing muy completo, en el que aprovechando las características de los distintos tipos de tests, librerías y lanzadores, podamos garantizar una calidad razonable de la aplicación y utilizar un flujo de trabajo muy productivo.

Al igual que pasa en el resto de aplicaciones, intenta encapsular todo lo que puedas en tests unitarios que no dependan de APIs externas (DOM o navegador) y que, por tanto, puedas ejecutar sin problema en node.js.

Sobre eso montar una capa de tests de integración que interactúen con el navegador como si fueran un usuario real da un extra de confianza que viene muy bien.

Posts relacionados:

  1. Cómo testear un API Web
  2. Testear Javascript con PhantomJS, QUnit y MSBuild
  3. Conceptos básicos para testear una base de datos

» Leer más, comentarios, etc...

Blog Bitix

Contenedores en Docker Swarm con volúmenes de datos persistentes usando REX-Ray y VirtualBox

abril 16, 2017 09:00

Salvo que un servicio sea sin estado o stateless los contenedores de Docker necesitan persistir datos y que estos sobrevivan a su terminación, como es el caso de un contenedor de una base de datos. Además en un cluster de nodos Docker hay que tener en cuenta que los datos deben estar accesibles para todos los nodos ya que un contenedor que usase los datos podría ser lanzado en cualquiera de ellos. REX-Ray es un sistema de almacenamiento en red que cubre estas necesidades, es simple de instalar, configurar y de iniciar. En el artículo muestro un ejemplo usando REX-Ray junto con Docker Swarm y VirtualBox.

Docker

Los contenedores de datos son efímeros, se crean y se destruyen y con ellos los datos que tuviesen en su sistema de archivos de modo que cualquier dato que queramos que sobreviva a la vida del contenedor ha de almacenarse de forma externa, este es el caso de los datos de una base de datos como PostgreSQL o MongoDB. Además usando Docker Swarm se plantea el problema de que hay varios nodos formando un cluster por lo que los datos han de estar accesibles independientemente del nodo en el que sea iniciado el contenedor que los utilice y significa que los datos no pueden estar almacenados en el nodo ya que un contenedor podría ser iniciado en cualquiera de ellos.

Así que los contenedores iniciados en un cluster de Docker Swarm que usen datos persistentes necesitan un sistema de almacenamiento en red externo a los contenedores y nodos. Una de las opciones disponibles es REX-Ray que ofrece una configuración sencilla y múltiples proveedores de computación entre las que están las más populares como Amazon EC2, Digital Ocean, Google Compute Engine, Azure e incluso VirtualBox.

En el siguiente ejemplo uso un cluster de nodos Docker, VirtualBox y REX-Ray para proporcionar volúmenes de datos persistentes para un contenedor que tiene una base de datos postgres basándome en el artículo previo Crear un cluster de contenedores Docker donde explicaba como crear un cluster de nodos con Docker Swarm.

Para la integración entre VirtualBox y REX-Ray hay que iniciar primero un servidor en el host que permite a REX-Ray hacer llamadas remotas a VirtualBox para que gestione los volúmenes de datos.

Si hay un firewall hay que permitir el tráfico para el puerto 18083, en mi caso que uso ufw creando la siguiente regla.

Con el cluster creado debemos instalar y configurar REX-Ray en cada uno de los nodos ejecutando varios comandos, un comando instala REX-Ray, otro crea el archivo de configuración en /etc/rexray/config.yml y finalmente otro inicia el servicio de REX-Ray. Algunas opciones que se indican en el archivo de configuración de REX-Ray es la ubicación en el host donde se guardan los volúmenes con el parámetro volumePath.

Instalación de REX-Ray en nodos de Docker Swarm con VirtualBox

Para probar la persistencia de datos usaré un stack iniciado de la misma forma que en artículo Iniciar un stack de servicios en un cluster de Docker Swarm pero con un contenedor de postgres que guarda los datos en un volumen de REX-Ray en /var/lib/postgresql/data. Para iniciar el stack el custer de Docker Swarm uso un archivo de Docker Compose con la definición del stack en formato YAML.

En la siguiente captura de pantalla se observa en que nodo ha sido iniciado el contenedor de postgres y que identificativo se le ha asignado.

Deploy del stack de postgres

En el stack el volumen de datos postgres está declarado y creado de forma externa. Usando VirtualBox con REX-Ray en el host o anfitrión se crea un archivo que contiene los datos del volumen. Al listar los volúmenes de datos además de los creados postgres y app están los de los discos duros de cada uno de los nodos identificados como disk.vmdk. El parámetro opt=size=5 indica que el volumen de datos es de una tamaño de 5GiB.

Volúmenes de datos

Para crear algunos datos en la base de datos hay que conectarse al contenedor y lanzar algunas sentencias SQL. Hay que obtener el identificativo del contenedor de postgres, iniciar un proceso bash, realizar la conexión a la base de datos con el cliente psql y lanzar las sentencias SQL.

Destruyendo el stack y volviéndolo a arrancar posiblemente Docker Swarm iniciará el contenedor en otro nodo del cluster pero los datos seguirán estando presentes en la base de datos postgres, se puede comprobar iniciando una nueva sesión bash en el nuevo contenedor, iniciando el cliente de psql y lanzando la consulta select de SQL o con el comando \dt para obtener las tablas de la base de datos, \d+ company para obtener una descripción de la tabla y la consulta SQL SELECT * FROM company;.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Blog Bitix

Escalar y actualizar un servicio de un cluster de Docker Swarm

abril 16, 2017 11:00

Ya tenemos un cluster formado por varios nodos con algún servicio ejecutándose en el cluster de Docker Swarm. Si surge la necesidad los servicios del cluster se pueden escalar cambiando el número de instancias de contenedores que forma el servicio para atender las necesidades computacionales o para ofrecer el servicio a más usuarios. Por otro lado, pasado un tiempo muy posiblemente se publicará una nueva imagen de los contenedores, el servicio se puede actualizar para que los contenedores utilicen esa nueva imagen.

Docker

Una vez que ya hemos creado un cluster de nodos con Docker Swarm y hemos desplegado algunos servicios ya sea directamente mediante comandos para crear servicios o mediante un stack con un archivo similar a un Docker Compose al cabo de un tiempo necesitaremos hacer otras operaciones. Dos de esas operaciónes básicas son escalar hacia arriba o hacia abajo un servicio cambiando el número de instancias de contenedores desplegadas o actualizar la imagen que utilizan los servicios por otra diferente posiblemente más nueva.

En la documentación de Docker están detallados y comentados los comandos para escalar un servicio. Por ejemplo, en el cluster de ejemplo formado por tres nodos, uno con el rol de manager y otros dos como worker, ejecutándose en VirtualBox y desplegando un servicio para el servidor nginx con inicialmente una réplica o instancia podemos escalar el servicio para que se cree alguna instancia o contenedor más del servicio con el siguiente comando docker service scale.

Al igual que cuando se crea un contenedor para un servicio en el cluster Docker Swarm si no se indica alguna restricción decidirá en qué nodos se crean las nuevas instancias o contenedores del servicio.

Servicio de nginx antes y después de escalarlo
Escalado del servicio de nginx

Por otro lado, una vez desplegados en un cluster algunos servicios llegará el momento en que queramos actualizar algún parámetro del servicio, uno de ellos será probablemente la imagen del servicio cuando se publique una nueva. En la página de documentación Aplicando actualizaciones a un servicio está explicada esta funcionalidad y los comandos junto con sus opciones que hay que utilizar.

En el ejemplo al crear el cluster se usa la última imagen de docker para nginx, en un entorno de producción es más recomendable establecer una versión en concreto para evitar que la imagen que se usa no varía desde que se prueba hasta que se despliega. El siguiente script actualiza la imagen a la versión nginx:1.10-alpine en todas las réplicas del servicio de nginx en el cluster.

Actualización de la imagen del servicio de nginx

Docker Swarm realiza el proceso de actualización siguiendo los siguientes pasos:

  • Detiene el primer contenedor o tarea a actualizar.
  • Programa la actualización del contenedor o tarea detenida.
  • Inicia una nueva tarea actualizado.
  • Si la tarea actualizada retorna su estado como RUNNING se espera un tiempo determinado y se inicia el proceso de actualización de una nueva tarea.
  • Si, en cualquier momento durante la actualización, una tarea retorna su estado como FAILED, se detiene la actualización.

Por defecto, el planificador actualiza las tareas o contenedores del servicio de uno en uno. Con la opción –update-parallelism se especifica el número de tareas del servicio que se actualizan simultáneamente. La opción –update-delay especifica el tiempo de espera desde que se actualiza la tarea de un servicio y la siguiente. Se puede describir como una combinación de segundos, minutos o horas, de modo que 1m30s indica una espera de 1 minuto y 30 segundos.

En los archivos en formato YAML de los stacks de Docker Compose hay una sección en cada servicio en el que se indica el número de contenedores que se desea que esté formado el servicio así como las opciones de paralelismo y tiempo de espera entre actualización. Para actualizar el stack basta con hacer de nuevo el deploy, ya sea la imagen usada, el número de réplicas u otros parámetros.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Variable not found

Cómo modificar la convención de nombrado de controladores en ASP.NET Core MVC

abril 16, 2017 09:23

ASP.NET Core MVCComo sabemos, tradicionalmente los controladores MVC son clases cuyo nombre, según la convención, debe finalizar por “Controller”, como en InvoiceController o CustomerController, y esta convención ha continuado en ASP.NET Core MVC, la edición más reciente del framework.

Sin embargo, desde las primeras versiones del framework la convención era modificable y podíamos adaptarla a nuestras necesidades aprovechando la extensibilidad del framework. De hecho, ya vimos hace muuucho mucho tiempo cómo hacerlo con la versión "clásica" de ASP.NET MVC, pero con la llegada de MVC Core las cosas han cambiado bastante.

En este post vamos a ver cómo modificar la convención de nombrado de controladores a algo más patrio: haremos que éstos puedan llamarse “ControladorDeXYZ”, como en ControladorDeFacturas o ControladorDeClientes. Es decir, si tenemos una clase como la siguiente, pretendemos que una petición hacia "/facturas/index" retorne el texto "ControladorDeFacturas.Index":
public class ControladorDeFacturas : Controller
{
public IActionResult Index()
{
return Content("ControladorDeFacturas.Index");
}
}
Por supuesto, podríamos hacer que esta clase fuera un controlador simplemente aplicándole el atributo [Controller], pero el objetivo de este post es aprender algo sobre el funcionamiento interno del framework, así que no vamos a quedarnos con esta solución tan sencilla ;)

Pero antes de ponernos a ello, permitidme aclarar que cambiar las convenciones de nombrado de controladores no es muy conveniente porque, aparte de romper el principio de la mínima sorpresa, hay herramientas que podrían dejar de funcionar correctamente, pero sin duda hacerlo ofrece una magnífica ocasión para profundizar un poco en los entresijos del framework ;)

1. Cómo ASP.NET Core MVC descubre los controladores de una aplicación

Cuando una aplicación MVC arranca, el framework analiza la aplicación para localizar los controladores. Para ello, recorre tanto el ensamblado principal (el proyecto MVC propiamente dicho) como los ensamblados vinculados y selecciona las clases que cumplen los requisitos para ser consideradas controladores:
  • Ser públicas
  • No ser abstractas
  • No presentar parámetros genéricos
  • No estar decoradas con el atributo [NonController]
  • Que su nombre acabe por "Controller" o tengan el atributo [Controller]
Nota: Observad que esta última condición, y el hecho de que en ningún sitio se exija heredar de la tradicional clase Controller, es la que hace posible que en ASP.NET Core MVC puedan existir controladores POCO.

Estas condiciones están definidas en ControllerFeatureProvider, una clase definida en el espacio de nombres Microsoft.AspNetCore.Mvc.Core.Controllers del framework, que es la encargada de decidir si un tipo puede o no ser un controlador a través de su método virtual IsController().

2. Cambiando el criterio de descubrimiento por defecto

Seguro que ya sabéis por dónde van los tiros ;) Si queremos hacer que MVC entienda que todas nuestras clases con el nombre "ControladorDeXYZ" son controladores, lo único que tenemos que hacer es escribir una versión personalizada de ControllerFeatureProvider, en cuyo método IsController() introduciremos la lógica que nos interese. Después, haremos que MVC lo tenga en cuenta registrándolo como proveedor de features, y ya tendremos el trabajo hecho.

Veamos a nivel de código en qué queda esto.

En primer lugar, vemos una posible implementación del nuevo feature provider. Vamos a aprovechar el proveedor existente, heredando de ControllerFeatureProvider y sobreescribiendo su método IsController(), de hecho con un código prácticamente idéntico al original, al que sólo añadirmos las condiciones que buscamos:
public class SpanishControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass || typeInfo.IsAbstract || !typeInfo.IsPublic
|| typeInfo.ContainsGenericParameters
|| typeInfo.IsDefined(typeof(NonControllerAttribute)))
{
return false;
}

if (!typeInfo.Name.StartsWith(
"ControladorDe", StringComparison.OrdinalIgnoreCase)
&& !typeInfo.IsDefined(typeof(ControllerAttribute)))
{
return false;
}
return true;
}
}
Tras ello, ya sólo nos falta indicar a MVC que puede utilizar este nuevo proveedor durante el arranque de la aplicación. Esto lo hacemos en el método Configure() de la clase de inicialización de ASP.NET, como sigue:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new SpanishControllerFeatureProvider());
});
}
Observad que no estamos eliminando el feature provider original de la colección FeatureProviders, de forma que nuestra aplicación descubrirá tanto los controladores que siguen la convención de nombrado por defecto ("XYZController"), como los que usan la nueva convención ("ControladorDeXYZ").
Tras finalizar el arranque de la aplicación, las clases que sigan nuestra nueva convención, como ControladorDeFacturas o ControladorDeUsuarios ya se encontrarán en el application model, que es la estructura de datos que contiene metainformación sobre el propio sistema, es decir, información sobre sus controladores, acciones, filtros, etc. Si no te suena, puedes echar un vistazo al post Convenciones personalizadas en ASP.NET Core MVC, donde ya hablamos de este tema hace algún tiempo.

3. ¿Problemas en el routing?

Si introducimos el código anterior y añadimos al proyecto el controlador ControladorDeFacturas que vimos algo más arriba, veremos que una petición como "/facturas/index" nos retorna un error 404.¿Por qué?

(Os doy tiempo para pensar… ;D)



Seguro que lo habéis visto claro ;) Por defecto, una petición como "/facturas/index" intentará ser procesada por un controlador llamado "facturas", pero para el sistema no existe un controlador con dicho nombre. Aunque nuestras clases hayan sido identificadas como controladores y estén en el application model, el framework no tiene forma de determinar cuál es el nombre del controlador real porque nos estamos saltando las convenciones estándar, es decir, no son nombres de la forma "XYZController", sino "ControladorDeXYZ".

Según la convención por defecto de ASP.NET Core MVC, el nombre del controlador asociado a una clase como "InvoiceController" será "Invoice"; en cambio, si se trata de un controlador POCO implementado en una clase llamada, por ejemplo, "HelloWorldClass", será el mismo nombre de la clase. Por tanto, en una clase como "ControladorDeFacturas" el nombre de controlador será "ControladorDeFacturas"; por esta razón, para acceder a la acción sí podremos utilizar una ruta como "/ControladorDeFacturas/index".

Por el mismo motivo tampoco funcionarán bien los métodos que utilizan el sistema de routing en sentido contrario, es decir, cuando deseamos generar la URL hacia una acción con helpers como Url.Action() o métodos similares.

Para solucionarlo, sin duda el método más sencillo y directo consiste en aplicar el atributo [Route] a los elementos que nos interese, como en el siguiente ejemplo:
[Route("facturas")]
public class ControladorDeFacturas : Controller
{
[Route("index")]
[Route("")]
public IActionResult Index()
{
return Content("ControladorDeFacturas.Index");
}
}
De esta forma, ya podremos acceder a la acción mediante peticiones dirigidas a rutas como "/facturas" o "/facturas/index". Y también funcionará bien en sentido inverso, puesto que el sistema de routing dispone de toda la información que necesita para generar las URL de acceso a las acciones.

Pero claro, no queremos quedarnos ahí ;) Además de ser una solución tediosa, internamente aún los controladores no tendrían el nombre correcto, y esto podría darnos problemas en otros puntos.

Vamos a ver cómo podemos modificar la forma en que el framework nombra los controladores para que al final el controlador implementado en "ControladorDeXYZ" sea denominado internamente "XYZ", y encaje bien en los esquemas de ruta por defecto.

4. Modificando el nombrado de los controladores

Pues supongo que habrá más fórmulas, pero se me ocurren al menos dos enfoques posibles para conseguir que los controladores sean nombrados apropiadamente, de acuerdo a nuestra nueva convención:
  • Desarrollar una convención personalizada que modifique el nombre del controlador.
  • Modificar la implementación del componente encargado de construir los metadatos de los controladores (ApplicationModelProvider) para que el nombre sea extraído teniendo en cuenta el nuevo criterio.
Veamos cada uno de ellos, que sin duda son nuevas ocasiones para seguir conociendo el framework por dentro ;)

4.1. Creando una convención personalizada

Ya vimos hace algún tiempo el concepto de convenciones en ASP.NET Core MVC. Básicamente se trata de un interesante mecanismo, integrado en el framework, que nos ofrece acceso al application model durante el arranque la aplicación con objeto de que podamos "retocar" la metainformación que la describe.

Dado que en esta metainformación se incluye el propio nombre del controlador, esta puede ser una buena forma modificar la manera en que internamente se nombran los controladores para alinearla con nuestra nueva directiva de nombrado.

Esto podría implementarse así de fácil:
public class SpanishControllerConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerName
            .StartsWith("ControladorDe", StringComparison.OrdinalIgnoreCase)
&& controller.ControllerName.Length > 13)
{
controller.ControllerName = controller.ControllerName.Substring(13);
}
}
}
Como se puede observar, la lógica que encontramos en Apply() simplemente modifica el nombre del controlador cuando detecta que cumple nuestra regla de nombrado. Por tanto, un controlador llamado "ControladorDeFacturas" lo dejaremos simplemente en "Facturas", que sería lo correcto.

Para registrar esta convención y hacer que sea aplicada durante el arranque, basta con añadirla a la colección Conventions de las opciones de configuración de MVC en la clase Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(opt =>
opt.Conventions.Add(new SpanishControllerConvention())
);
}

4.2. Creando un application model provider personalizado

La solución anterior es válida, e incluso diría que recomendable, para conseguir los objetivos que nos hemos propuesto.

Pero como el objetivo es profundizar en el framework, vamos a ver otro enfoque que requiere que nos remanguemos un poco más y nos sumerjamos en las interioridades de ASP.NET Core MVC. En particular, vamos a "puentear" ligeramente el proceso de creación de metainformación sobre controladores en el application model, sobrescribiendo el nombre de controlador que por defecto le asigna el framework para que se atenga a nuestra nueva convención.

La información sobre controladores que es añadida al application model es obtenida a través del método CreateControllerModel() de la clase DefaultApplicationModelProvider del framework. Este método retorna un objeto de tipo ControllerModel que contiene todo tipo de datos sobre el controlador: acciones, filtros, propiedades… y también el nombre del controlador, que es el dato que nos interesa en este momento.

Por tanto, vamos a heredar de DefaultApplicationModelProvider y sobrescribiremos su método CreateControllerModel(), de forma que seamos nosotros los que decidamos el nombre que queremos dar a los controladores:
public class SpanishControllerApplicationModelProvider 
    : DefaultApplicationModelProvider
{
public SpanishControllerApplicationModelProvider(IOptions<MvcOptions> opt)
       : base(opt)
{
}

protected override ControllerModel CreateControllerModel(TypeInfo typeInfo)
{
var model = base.CreateControllerModel(typeInfo);
if(model.ControllerName
              .StartsWith("ControladorDe", StringComparison.OrdinalIgnoreCase)
&& model.ControllerName.Length > 13)
{
model.ControllerName = model.ControllerName.Substring(13);
}
return model;
}
}
Como se puede observar, la lógica del nuevo método CreateControllerModel() es bastante sencilla y parecida a la que vimos anteriormente al implementar la clase SpanishControllerConvention. Detectamos que se trata de un controlador que se atiene a nuestra convención, como en "ControladorDeFacturas", y eliminamos el prefijo, dejándolo como "Facturas".

Una vez hecho esto, debemos informar al framework de que debe usar este proveedor en lugar del inicial, por lo que eliminaremos del registro de dependencias la instancia de DefaultApplicationModel e insertaremos nuestro nuevo provider.

El método ConfigureServices() de la clase de inicialización quedaría como sigue:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new SpanishControllerFeatureProvider());
});

var svc = services.FirstOrDefault(
              s => s.ServiceType == typeof(IApplicationModelProvider));
if (svc != null)
   {
services.Remove(svc);      
services.AddTransient
<IApplicationModelProvider, SpanishControllerApplicationModelProvider>();
   }
}

5. Y recapitulando un poco…

En este post hemos visto cómo modificar la convención de nombrado por defecto de controladores, de forma que en lugar de usar el tradicional "XYZController" en las clases podamos utilizar "ControladorDeXYZ", y que todo siga funcionando como sería de esperar.

Para ello hemos tenido que:
  • Modificar el proceso de descubrimiento de controladores, haciendo que clases que comiencen por "ControladorDe" sean consideradas controladoras.
  • Modificar la forma de obtención del nombre de controlador y hacerlo consistente con nuestra nueva convención de nombrado, es decir, que el controlador implementado en "ControladorDeXYZ" se denomine internamente "XYZ".
Como comentamos al principio, no es algo que tenga mucha utilidad en el día a día, pero espero que os haya resultado interesante para comprender mejor cómo funcionan las cosas por dentro y conocer algunos puntos de extensibilidad del framework.

Publicado en Variable Not Found.

» Leer más, comentarios, etc...

Adrianistán

Tutorial de Rocket, echa a volar tus webapps con Rust

abril 15, 2017 08:10

Previamente ya hemos hablado de Iron como un web framework para Rust. Sin embargo desde que escribí ese post ha surgido otra librería que ha ganado mucha popularidad en poco tiempo. Se trata de Rocket. Un web framework que propone usar el rendimiento que ofrece Rust sin sacrificar la facilidad de uso de otros lenguajes.

Rocket lleva las pilas cargadas

A diferencia de Iron, Rocket incluye bastantes prestaciones por defecto con soporte para:

  • Plantillas
  • Cookies
  • Formularios
  • JSON
  • Soporte para rutas dinámicas

Un “Hola Mundo”

Rocket necesita una versión nightly del compilador de Rust. Una vez lo tengas creamos una aplicación con Cargo.

cargo new --bin rocket_app

Ahora modificamos el fichero Cargo.toml generado en la carpeta rocket_app para añadir las siguientes dependencias:

rocket = "0.2.4"
rocket_codegen = "0.2.4"
rocket_contrib = "*"

Editamos el archivo src/main.rs para que se parezca algo a esto:

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "El cohete ha despegado"
}

fn main() {
    rocket::ignite().mount("/",routes![index]).launch();
}

Con esto iniciamos Rocket y dejamos a la función index que gestione las peticiones GET encaminadas a /. El servidor devolverá El cohete ha despegado.

Ahora si ejecutamos cargo run veremos algo similar a esto:

Vemos que el servidor ya está escuchando en el puerto 8000 y está usando todos los cores (en mi caso 4) del ordenador.

Configurar Rocket

Rocket dispone de varias configuraciones predeterminadas que afectan a su funcionamiento. Para alternar entre las configuraciones debemos usar variables de entorno y para modificar las configuraciones en sí debemos usar un fichero llamado Rocket.toml.

Las configuraciones por defecto son: dev (development), stage (staging) y prod (production). Si no indicamos nada, Rocket se inicia con la configuración dev. Para arrancar con la configuración de producción modificamos el valor de ROCKET_ENV.

ROCKET_ENV=prod cargo run --release

Sería el comando para arrancar Rocket en modo producción. En el archivo Rocket.toml se puede modificar cada configuración, estableciendo el puerto, el número de workers y parámetros extra pero no vamos a entrar en ello.

Rutas dinámicas

Rocket soporta rutas dinámicas. Por ejemplo, si hacemos GET  /pelicula/Intocable podemos definir que la parte del nombre de la película sea un parámetro. Esto hará que la función encargada de /pelicula/Intocable y de /pelicula/Ratatouille sea la misma.

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/pelicula/<pelicula>")]
fn pelicula(pelicula: &str) -> String {
    format!("Veo que te gusta {}, a mi también!",pelicula)
}

#[get("/")]
fn index() -> &'static str {
    "El cohete ha despegado"
}

fn main() {
    rocket::ignite().mount("/",routes![index,pelicula]).launch();
}

Los argumentos de la función son los parámetros de la petición GET. ¿Qué pasa si no concuerda el tipo de la función con lo que se pasa por HTTP? Nada. Sencillamente Rocket ignora esa petición, busca otra ruta (puede haber sobrecarga de rutas) y si encuentra otra que si satisfaga los parámetros será esa la escogida. Para especificar el orden en el que se hace la sobrecarga de rutas puede usarse rank. En caso de no encontrarse nada, se devuelve un error 404.

POST, subir JSON y formularios

Rocket se integra con Serde para lograr una serialización/deserialización con JSON inocua. Si añadimos las dependencias serde, serde_json y serde_derive al fichero Cargo.toml podemos tener un método que acepete una petición POST solo para mensajes del tipo application/json con deserialización incorporada.

#![feature(plugin)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate rocket;

use rocket_contrib::{JSON, Value};

#[derive(Serialize,Deserialize)]
struct User{
    name: String,
    email: String
}

#[post("/upload", format="application/json", data="<user>")]
fn upload_user(user: JSON<User>) -> JSON<Value> {
    JSON(json!({
        "status" : 200,
        "message" : format!("Usuario {} registrado con éxito",user.email)
    }))
}

fn main() {
    rocket::ignite().mount("/",routes![upload_user]).launch();
}

Si el JSON no se ajusta a la estructura User simplemente se descarta devolviendo un error 400.

Lo mismo que es posible hacer con JSON puede hacerse con formularios usando el trait FromForm.

#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate rocket;

use rocket_contrib::{JSON, Value};
use rocket::request::{FromForm, Form};

#[derive(FromForm)]
struct User{
    name: String,
    email: String
}

#[post("/upload", data="<user>")]
fn upload_user(user: Form<User>) -> String {
    format!("Hola {}",user.get().name)
}

fn main() {
    rocket::ignite().mount("/",routes![upload_user]).launch();
}

Errores

En Rocket, como es lógico, es posible crear páginas personalizadas para cada error.

#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[get("/")]
fn index() -> &'static str {
    "El cohete ha despegado"
}

#[error(404)]
fn not_found() -> &'static str {
    "La página no ha podido ser encontrada"
}

fn main() {
    rocket::ignite().mount("/",routes![index]).catch(errors![not_found]).launch();
}

La lista de métodos que manejan errores hay que pasarla en el método catch de rocket::ignite

Respuestas

Rocket nos permite devolver cualquier cosa que implemente el trait Responder. Algunos tipos ya lo llevan como String, File, JSON, Option y Result. Pero nada nos impide que nuestros propios tipos implementen Responder. Con Responder tenemos el contenido y el código de error (que en la mayoría de casos será 200). En el caso de Result es muy interesante, pues si Err contiene algo que implementa Responder, se devolverá la salida que implemente también, pudiendo así hacer mejores respuestas de error, mientras que si no lo hacen se llamará al método que implemente el error 500 de forma genérica. Con Option, si el valor es Some se devolverá el contenido, si es None se generará un error 404.

#![feature(plugin,custom_derive)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate rocket_contrib;
extern crate rocket;

use rocket::response::{self, Responder, Response};
use std::io::Cursor;
use rocket::http::ContentType;

struct Pelicula{
    nombre: &'static str,
    pais: &'static str
}

impl<'r> Responder<'r> for Pelicula{
    fn respond(self) -> response::Result<'r> {
        Response::build()
        .sized_body(Cursor::new(format!("La película {} se hizo en {}",self.nombre,self.pais)))
        .header(ContentType::new("text","plain"))
        .ok()
    }
}

#[get("/pelicula/<pelicula>")]
fn pelicula(pelicula: &str) -> Result<Pelicula,String> {
    let intocable = Pelicula{
        nombre: "Intocable",
        pais: "Francia"
    };
    let madMax = Pelicula{
        nombre: "Mad Max",
        pais: "Estados Unidos"
    };
    match pelicula {
        "Intocable" => Ok(intocable),
        "Mad Max" => Ok(madMax),
        _ => Err(format!("No existe esa película en nuestra base de datos"))
    }
}

#[get("/")]
fn index() -> Result<String,String> {
    Err(format!("No implementado"))
}

#[error(404)]
fn not_found() -> &'static str {
    "La página no ha podido ser encontrada"
}

fn main() {
    rocket::ignite().mount("/",routes![index,pelicula]).catch(errors![not_found]).launch();
}

Este ejemplo para /pelicula/Intocable devolverá: La película Intocable se hizo en Francia mientras que para /pelicula/Ratatouille dirá No existe esa película en nuestra base de datos.

También es posible devolver plantillas. Rocket se integra por defecto con Handlebars y Tera, aunque no es muy costoso añadir cualquier otra como Maud.

Conclusión

Rocket es un prometedor web framework para Rust, bastante idiomático, que se integra muy bien con el lenguaje. Espero con ansia las nuevas veriones. Es posible que la API cambie bastante hasta que salga la versión 1.0, no obstante así es como ahora mismo funciona.

La entrada Tutorial de Rocket, echa a volar tus webapps con Rust aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Picando Código

My Moon – Corto de ciencia ficción hecho completamente con Software Libre

abril 13, 2017 05:03

El siguiente corto fue producido por Nikolay Prodanov con herramientas 100% libres: Blender, Krita, Inkscape, y OpenMPT. Usó Blender para la animación 3D, Krita para los gráficos en mapa de bits, Inkscape para los gráficos vectoriales y OpenMPT para la música. El trabajo además es publicado bajo licencia Creative Commons:

Un excelente ejemplo de que no es necesario pagar miles de dólares en licencias de software privativo para producir arte de calidad.

» Leer más, comentarios, etc...

Blog Bitix

Ejemplo sensor de golpes y detector de movimiento en la Raspberry Pi con Java

abril 13, 2017 09:00

Raspberry Pi
Java

Otro par de sensores que incluye el kit de introducción a la electrónica para la Raspberry Pi son un sensor de golpes o tilt y un detector de movimiento. El funcionamiento de ambos es muy similar, utilizando un pin GPIO como entrada se recibe si el sensor de golpes está activo o si el detector de movimiento ha detectado movimiento. El sensor de golpes es una bolita de mercurio encerrada en una ampolla de cristal. Como el mercurio a temperatura ambiente su estado es líquido puede moverse y como es un metal puede conducir la electricidad cuando está en una determinada posición entre dos filamentos.

El sensor de golpes necesita de tres cables uno para la corriente de 3.3V, otro para tierra y finalmente otro que se conecta como entrada a un pin GPIO. Usando varios cables hembra-hembra y macho-macho hacemos las conexiones entre el sensor y la placa de pruebas sin soldadura o breadboard.

Sensor de golpes y detector de movimiento

Usando la librería diozero para controlar los pines GPIO desde un programa implementado con Java detectamos si el sensor está activo o no según la posición de la bolita de mercurio. El ejemplo consiste en encender un diodo LED que ya mostré en un artículo anterior de esta serie sobre electrónica cuando el sensor tilt esté activo. Usaré el pin 18 para el diodo LED y el pin 21 para el sensor según la nomenclatura de Broadcom. Los pines serían el 12 según la nomenclatura del header y 1 según la nomenclatura de wiringPi para el diodo LED y 13 y 2 para el sensor tilt.

Cableado sensor de golpes

En el siguiente vídeo se aprecia como cuando al cambiar de posición del sensor de movimiento se mueve la bolita de mercurio y el diodo LED de ejemplo se enciende y apaga.

El detector de movimiento en teoría es similar en funcionamiento al tilt y la librería diozero proporciona la clase MotionSensor para hacer más sencillo su uso. Digo en teoría porque no he conseguido hacerlo funcionar y he revisado varias veces las conexiones mostradas en otros ejemplos incluido el ejemplo de osoyoo, no se si es porque me falta algo más que debo tener en cuenta y que no conozco o el sensor no funciona viniendome estropeado. Cuando el sensor detecta movimiento cambia el voltaje de su pin GPIO de datos. Los otros dos pines que utiliza son uno para el voltaje de 5V y el de tierra.

El programa Java para el sensor de movimiento es similar al sensor tilt e igualmente encendería o apagaría un diodo LED cuando detecta movimiento.

Cableado sensor de movimiento

Ambos ejemplos pueden usarse con los siguientes comandos cambiando la dirección IP de la Raspberry Pi y el directorio de la misma a donde se suben los ejemplos.

El siguiente artículo de la sería será sobre cómo usar un servo motor que es diferente de un motor que gira constantemente.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

WhatsApp Status Monitor

abril 13, 2017 08:00

Después de publicar Telegram Status Monitor, he recibido bastantes peticiones de gente interesada en esa misma solución, pero para WhatsApp. Es decir, poder saber los cambios de estado de un contacto, pese a que tenga oculta la hora de la última actividad. Para eso está WhatsApp Status Monitor. Se encarga de supervisar el estado del […]

La entrada WhatsApp Status Monitor aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 277

abril 10, 2017 06:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET/.NET Core

ASP.NET/ASP.NET Core

Azure / Cloud

Data

Html/Css/Javascript

Visual Studio/Complementos/Herramientas

Cross-platform

Otros


Publicado en Variable not found

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Telegram Status Monitor

abril 09, 2017 03:31

Nunca os he hablado aquí de Telegram, un sistema de mensajería instantánea, que además de gratuito, es de código abierto. Como WhatsApp, está disponible para Windows Phone, iOS, Android y Web. Pero además ofrece versiones de escritorio para Windows, OS/X, y Linux, lo cual es una gran comodidad. Sin necesidad de escanear códigos QR en […]

La entrada Telegram Status Monitor aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Sponsors

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin