Crea tus propias webs dinámicas en C++ de forma fácil y usando plantillas para no compilar a cada cambio

Publicado el 19 septiembre 2016 por Gaspar Fernández Moreno @gaspar_fm


Esto forma parte de un pequeño proyecto que mantengo desde hace algo más de un año. El objetivo es crear un sistema con el que servir webs desde C++, que sea más o menos fácil de mantener y generar, flexible, que soporte enrutadores, plantillas, hosts virtuales con el fin de poder utilizarlo para diferentes tipos de aplicaciones a modo de panel de control web.

Sobre todo está pensado para aplicaciones internas y no para desarrollos web públicos, por temas de seguridad y rendimiento cuando la carga aumenta considerablemente, aunque sí que podría ser utilizado sin problemas en segundo plano por nuestras aplicaciones web por eso de que en ciertas operaciones, un programa hecho en C++ rendirá mucho mejor que cualquier Javascript, Java, PHP, etc. Será más difícil de hacer y optimizar, pero correrá con más soltura y aprovechará la memoria de manera más eficiente.

Para conseguir esto, he utilizado dos proyectos de los que ya os he hablado en el pasado: Glove, que es una envoltura para sockets en C++11 con el que podemos crear un servidor web rápidamente ( GitHub) y Silicon, un sistema de plantillas que nos permite separar la salida web de nuestro código en C++ (y eso nos permite también hacer pequeños cambios en la salida sin tener que compilar de nuevo). ( Github).

Ambos proyectos están juntos para poder probarlos desde un proyecto en GitHub llamado SiliconGlove, de todas formas, aquí explico un poco cómo funciona todo.

El sistema tiene poco más de 100 líneas (sin contar las plantillas, que hice basándome en el tema StartBootstrapLandingPage.

Inicialización

Lo primero que tenemos que hacer es iniciar el servidor web con esta línea:

Creamos el objeto serv, que será nuestro servidor. En este caso, trabajaremos en el puerto 8080. El segundo parámetro será la IP desde la que escuchamos, con lo que podemos limitar la escucha a un dispositivo concreto (si tenemos varios dispositivos de red), y el tercer parámetro será el tamaño del buffer, podremos jugar con este parámetro para conseguir mejores resultados, pero 2048 no es un mal valor).

Luego, debemos inicializar el sistema de plantillas, en este caso, estableceremos las rutas internas de css y js (para encontrarlos fácilmente, así como establaceremos ciertos valores por defecto. Todo esto, si miramos el proyecto en GitHub, lo podremos ver dentro de la función setypSiliconGlobals():

Para Silicon tenemos otro buffer de datos y, si vemos que el tamaño de nuestras plantillas aumenta mucho, debemos pensar en aumentar este valor de 65535. Esto existe para optimizar un poco en tiempo y en memoria el sistema. Casi todas las palabras clave dentro de setGlobalKeyword() serán palabras que se sustituirán directamente en la plantilla, es decir, cuando en la plantilla llamemos a TwitterLink, saldrá mi enlace de Twitter automáticamente. Aunque encontramos valores con una barra baja (_) delante que serán configuraciones del sistema de plantillas, por ejemplo _renderResources, define si un recurso se pinta nada más llamarlo o no. En este caso, como no se pintan, podemos acumular todos los JS y CSS dentro de las plantillas, y sólo se harán efectivos cuando llamemos a una orden específica para pintarlos. Lo veremos más adelante.

Enrutador

Una parte importante de un sistema web, si no queremos sufrir mientras lo implementamos es un enrutador. Ya que todas las peticiones web entrarán al mismo sitio, tenemos que definir quién procesará cada una y es este enrutador el que se encarga de ello. Debemos definir qué hacer cuando llamemos a http://host/css/xxxx , http://host/js/xxxx , http://host ... para ello podemos definir qué función se llamará para procesar cada una de las llamadas, de la siguiente manera:

Así cuando llamemos a la web principal, lo procesará una función llamada webIndex (la veremos más adelante). Aunque esto no tiene gracia si las rutas fueran fijas, es decir, si definimos /css/estilos.css y tuviéramos que procesarlo de forma fija, siempre, porque si incluimos varios archivos css en la misma ruta deberíamos definirlos uno a uno. Por ello, si introducimos un símbolo de dólar ($) en la ruta, el sistema lo tomará como una palabra clave. En otras palabras, se creará un parámetro dinámico al que nos referiremos como 'anycon', 'anythink', o 'filename', y luego la función que procese la petición sólo tendrá que jugar con el nombre que le hemos dado a la palabra clave. Esto nos puede dar mucha flexibilidad (luego veremos lo fácil que es desde la función hello).

Por otro lado, el sistema tiene funciones predefinidas, como servir un fichero directamente. Si es un CSS/JS y no vamos a hacer nada con él, lo podemos devolver directamente, para ello tenemos la función fileServerExt(). En este caso, con bind() le pasamos un argumento extra al callback, en este caso la ruta donde de verdad se encuentra el archivo. Con esto, aunque a nuestro servidor le pidan algunos archivos desde /fonts/css/fuente.ttf , en realidad lo estaremos pidiendo a resources/fonts/css/fuente.ttf , por lo que nuestra aplicación puede tener todo mucho mejor organizado.

Sirviendo el index

Para servir el index, en este caso, sólo tenemos que cargar un archivo de plantilla base (layout), sera una plantilla general para todas las subpáginas, aunque ahora sólo tenemos una, y luego cargará una plantilla específica para la página actual. Además, definiremos palabras clave locales para nuestra plantilla (cuando ésta se procese, se juntarán las palabras clave locales y las globales). Para servir la página haremos lo siguiente:

Cogiendo palabras clave del enrutador

Esto lo podemos ver en la función hello():

Vemos que request, tiene una variable llamada special con la que accede a las palabras clave del enrutador. En este ejemplo, las listamos, pero podríamos poner perfectamente:

Y esto nos puede ayudar a tener ordenados los argumentos de entrada, comprobar que se pasan y procesar una salida adecuada.

Plantillas

Dentro de las plantillas, podemos incluir CSS y JS donde queramos:

{!includeCss file="bootstrap.min.css" /}
{!includeCss file="landing-page.css" /}
{!includeCss file="http://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" /}
{!includeJs file="jquery.js" /}
{!includeJs file="bootstrap.min.js" /}

Del mismo modo, podemos hacer el render de CSS y JS donde queramos (porque _renderResources vale 0:

Y, como también vemos, incluir variables con {{nombreDeVariable}}. Del mismo modo, si estamos en una plantilla base o layout, podemos incluir la información procesada de la plantilla de sección si incluimos {{contents}} , el texto se puede configurar si desde nuestro programa en C llamamos a setContentsKeyword().
Incluso incluir otras plantillas:

{!block template="blocks/nav.tpl"/}

Sólo queda compilar, en el proyecto en GitHub, tenéis un ejemplo de Makefile que podéis utilizar para hacer pruebas rápidas.

Cosas que se pueden hacer

Aunque en el ejemplo sólo se demuestran enrutadores y plantillas, en Glove, se pueden utilizar conexiones seguras HTTPS (sólo hay que introducir los certificados), siliCon soporta la creación de funciones para procesar contenidos, condiciones, colecciones y muchas más cosas que nos harán el desarrollo mucho más fácil. Sólo tenemos que ver los ejemplos incluidos en los dos proyectos.

Si queréis un panel de control para vuestros proyectos, os recomiendo echarle un ojo a la plantilla AdminLTE. Tiene muchas características interesantes y podemos integrarla perfectamente dentro de Glove+Silicon para nuestros sistemas de control.

Un extra más

Si queremos incluir dentro de nuestro proyecto web una llamada a Glove. Por ejemplo, desde una web de producción, o desde un subdominio. Una forma muy recomendable de hacerlo es, por ejemplo, a través de Apache. De esta forma, Apache se encargará de la seguridad en las peticiones, y de pasarnos sólo lo que debe (sin que el usuario final interactúe directamente con nuestro programa), además, podemos tener varios servicios corriendo y acceder a ellos desde una misma dirección web (sin necesidad de cambiar puertos).

Para ello tenemos que activar en Apache los módulos proxy y proxy_http:

Module proxy already enabled

Enabling module proxy_http.

Y dentro de nuestro VirtualHost añadir la línea que comienza por ProxyPassMatch:

ServerAdmin webmaster@localhost
DocumentRoot /home/gaspy/www
ProxyPassMatch ^/glove/(.*)?$ http://127.0.0.1:8080/$1

De esta forma, nosotros, desde fuera, accederemos a http://localhost/glove/ y Apache, internamente, accederá a http://localhost:8080. Así, podemos cerrar el puerto 8080 para prevenir ataque externos.