martes, 28 de abril de 2015

¿Por qué programar en Python y Django?

Nunca me hice un post de esto y, la verdad, en este Abril del 2015 hay que actualizar un poco lo que se anda diciendo en internet sobre ciertos frameworks que están ganando popularidad.

Desde 1998 que venimos usando PHP ("Para Hacer Paginas" xD) para construir nuestros sitios. Pero lo cierto es que las cosas cambian y evolucionan: los sistemas comenzaron a hacerse en Java, .NET, y desde hace años (digamos unos 8, aunque bien marcadamente 5) Python.

PHP se hizo famoso por ser el primero y el más difundido. También el más fácil de instalar si tus necesidades son simples (un sitio, sin mas cosas dando vueltas). Además, con esto de los hosts virtuales (tanto para el HTTP como para el SMTP/POP), es por lejos más facil montar servicios de hosting compartido que con sistemas con Python o Java (cuyos mantenimientos requieren acceso a un usuario en servidor que no muchos hostings están dispuestos a proveer).

Pero PHP es molesto en sintaxis (en mi opinión), tardó mucho en ganar determinadas capacidades hoy presentes en PHP 5.3 y PHP 5.4, y tiene una comunidad con una mala cultura de programación en la que prácticamente nos enseñan a construir código que accede a bases de datos introduciendo cuanta inyección SQL se nos ocurra (¿quién no habrá visto esos tutoriales en PHP que componen un SQL para MySQL como "select * from employees where id = $id"?).

Es cierto que, con el tiempo, aparecieron muchisimos frameworks o CMS ("o" dije, porque por lo general nunca son lo mismo: un framework es una estructura marco para que puedas hacer un programa a tu medida, mientras que un sistema de administración de contenido está pensando para que puedas fácilmente administrar contenido sobre una base ya hecha) como los tres mosqueteros del PHP: Drupal, Joomla, Wordpress. Pero lo cierto es que tienen una curva de aprendizaje muy dura, e incluso al día de hoy tienen más fallas de seguridad que los frameworks modernos. Adicionalmente, manejar el cache es complicado (a mí se me hizo un infierno instalar el APC todas las veces, especialmente si hablamos de los casos en los que tuve que usar Windows). Tambien es cierto que los hostings de PHP comenzaron a regalar sus "1-Click installers" para desplegar rápido esos sistemas.

Al parecer el usuario tiene todo regalado ¿no?. Al parecer, PHP y su comunidad nos dan la sensación de que no necesitamos realmente de un programador (sino ya solamente de un diseñador web y especialistas en comunicación y medios digitales) ¿no?. Es decir ¿qué tan difícil puede ser? Incluso en la Casa Blanca (whitehouse.gov) hay un CMS atendiendo.

Todo eso es cierto PERO:
  1. Hay programadores y expertos de seguridad (muy buenos - imagínense que se tienen que aguantar hasta a hackers del ISIS o M.E.C.A.) tras el sitio de la Casa Blanca.
  2. Realmente si tu intención es "salirte del libreto" con uno de esos sistemas, programar es por lejos más difícil que en un framework WEB normal.
  3. El administrador trasero de esos frameworks es tan complejo para el usuario final que frecuentemente se necesitan expertos en Drupal, Joomla, y Wordpress solamente para utilizarlos.
Finalmente, volvimos a necesitar especialistas y nos quedamos con tecnologías más limitadas e inseguras.

Entonces ¿qué usar para desarrollar sitios?

En realidad depende lo que queremos. Mi consejo es no recurrir a balas de plata. Cada uno de ellos tiene su propia metodología y limitaciones, y siempre va a depender su idoneidad de lo que queramos desarrollar. Si querés un sitio WEB, te toca ponerte a desarrollar. Si querés presentar fotos, formularios de contacto, multimedia en cualquier formato, y sin procesamientos complejos entonces un CMS te sirve perfectamente. Y si querés comunicación en tiempo real, directamente deja de servirte el PHP en comparación a otras tecnologías.

¿Y dónde entra Python acá?

Realmente Python (así como Java y .NET) es una plataforma (Python 2, Python 3, en sus varias implementaciones existentes) para correr cualquier tipo de programa, mientras que PHP es, tradicionalmente, ejecutado DENTRO del contexto de una petición web (sí, ya se que me van a venir con winbinds y con el servidor interno que existe desde PHP 5.4, pero al menos este último es un chiste en cuanto a performance). Esta limitación hace que el diseño de los frameworks sea más molesto, y que cada petición tenga mucha carga en memoria, terminando el usuario obligado a usar APC o cualquier caché que encuentre).

Con el tiempo aparecieron dos frameworks muy usados (entre otros): Django y web2py. Ambos tienen un portal administrativo y, en particular web2py, hasta un editor de código y modelos (digamos que es su propio entorno de desarrollo).

Django corre sobre Python y se originó como un CMS (sus autores lo llamaban "El CMS" a priori, antes de darle nombre), pero evolucionó hasta convertirse en un framework.

En lo personal nunca usé web2py seriamente, por lo que no podría compararlos a priori. Pero sí puedo compararlos contra PHP y sus "soldados" y mostrando por qué es superior en todo sentido a la hora de desarrollar sitios desde cero.

Algunos frameworks sumamente complicados de PHP son Propel, Doctrine, y Simfony (aunque siguen en mantenimiento, la misma sociedad los va deprecando). Otros son más fáciles y con algunos asistentes, como Yii y Laravel. Al final quedan los CMS que ya mencionamos.

Instalar los tres primeros es un embole y se necesita que el servidor se encuentre configurado con librerías especiales. Instalar los del medio es más fácil. Instalar los CMS es, en general, extremadamente fácil. Sin embargo, a medida que se vuelven fáciles también se vuelven limitados o pesados en memoria. No digo que a priori deba ser así, que sea ley de vida, pero evidentemente la comunidad de PHP tiene un estilo de desarrollo en donde esto ocurre casi en la totalidad de los casos.

Python es más fácil en todo sentido, aunque requiere permisos de acceso a un shell que son más difíciles de obtener (razón por la cual aún hoy pocos servicios ofrecen python en sus planes básicos). Es un lenguaje poderoso y, generalmente, podemos levantar varios procesos en un servicio de python, por lo que podemos valernos de un sistema distribuido para hacer lo que necesitemos.

Django en particular succiona lo mejor de todos los mundos: Es más versátil que Laravel, Simfony, Doctrine, y Propel (y más fácil de manipular el sistema de ORM), más administrable que Yii, y más fácil de usar, desarrollar, acoplar, y "plantillar" que Drupal mismo (y más eficiente: los ganchos de drupal te comen la memoria).

¿Por quéser tan fanboy?

Aunque lo intento, por lo general no puedo elegir otra cosa para programar. Siempre que me enfrento a requerimientos que no sean cambiar una simple imagen sino rehacer todo un sitio, Django es mi opción (me vuelco a Laravel si estoy obligado a usar PHP).

Y es que logré hacer una aplicación para Facebook, con sus artes, administrable, con capacidad de descargar un excel, capacidad de mandar e-mails de contacto, y otros chiches por ahí tomándome un tiempo de entre uno y dos días completos de trabajo. Este proceso lo hice al menos seis veces, metiendo seis aplicaciones en dos cuentas de OpenShift.

Leí decir que "Django y Drupal son dos bestias diferentes que no se pueden comparar". Eso es cualquier mierda. Ambas terminan usándose para sitios similares (y en particular Django nació como un CMS), y tienen ventajas y desventajas.

Drupal:
  • Instalarlo es fácil.
  • Viene con algunas plantillas predeterminadas.
Django:
  • Programar es más fácil. Desarrollar módulos ("aplicaciones") es increíblemente fácil.
  • El administrador es más liviano (y en parte limitado) que el Drupal, pero altamente (y fácilmente) personalizable.
  • Los aportes de la comunidad de Django son tan fuertes que fácilmente se pueden comparar a los aportes de la comunidad de Drupal y superarlos.
  • Existen frameworks que nos ofrecen un CMS sobre Django (como django-cms y wagtail), que le ofrecen a Django las ventajas que tiene Drupal, pero mantienen la facilidad para programar que nos ofrece Django.
Si quieren decir que existen cosas que no se pueden comparar, que muestren dos dominios de aplicación diferentes. Un ejemplo es OpenERP vs. Django: ambos tienen ventajas y desventajas, y cuestiones de diseño, que son totalmente irreconciliables e incomparables entre sí. Pero si tratamos el mismo dominio de negocio, entonces no es siquiera cuerdo el "agnostificar" la comparación de dos tecnologías. Es una "falsa modestia" que, realmente, no solo no aporta nada a los profesionales en la materia sino que es perjudicial.

Django es mejor que Drupal y sus amigos. #DealWithIt.

La peor parte de esto es: realmente me gustaría que existan tecnologías así de cómodas en otros lenguajes (principalmente en PHP), pero por limitaciones del lenguaje o por cultura, es difícil lograr que aparezcan, y no tengo el tiempo para desarrollarme mi propio framework para que eso ocurra. Por lo que me sigo quedando con Django como preferencia (con Python como lenguaje, obvio).

Finalmente, todo esto se trata de gustos (aunque PHP con algun CMS o Framework es menos performante que Django), pero también se trata de que nosotros hagamos más cómodo nuestro trabajo, y en poco tiempo sacar algo que quede vivo y funcional, y no un feto a medio abortar o un homúnculo de Fullmetal Alchemist. Deberíamos ponernos a revisar qué es más fácil de mantener a largo plazo y, en particular, a mí Django se me hace mucho más fácil y más estable que los Drupals que tuve que mantener (que siempre me implicaban quejas de parte del hosting en relación a la seguridad y a la memoria o procesamiento que consumen).

Y para rematar, quisiera remarcar los me gusta y los no me gusta de Django, ya que lo justo es que exponga ambas cosas:

Me gusta:

  • ORM fácil, super intuitivo, y poderoso.
  • Administrador comparablemente fácil y poderoso (aún con sus falencias). Generalmente, este punto agrega un valor determinante a la hora de decidir usar Django como framework de desarrollo.
  • Sistemas de plantillas bastante lindos. Desde 1.8, también se da soporte a sistemas más poderosos (y que nunca usé) como Jinja.
  • Librerías de terceros bastante poderosas, como Django REST Framework ("djangorestframework"), ideal para desarrollar puntos REST.
  • Migraciones! Desde 1.7, muy poderosas aunque aún pueden mejorar. En versiones anteriores se usaba la librería "django-south" para realizar migraciones.
  • Una consola por lejos mejor que "drush" y que "artisan" (para Drupal y Laravel, respectivamente). Altamente personalizable (tanto o más que "artisan").
  • Sistema muy cómodo de MVC (o algo similar), y direccionamiento y referenciamiento de URLs de manera "cómoda".
  • Alta compatibilidad con las bases de datos más modernas (sí, también existe un soporte comunitario para Oracle).
  • La documentación oficial es una de las mejores documentaciones oficiales que leí en mi vida.
  • Buen soporte para agregar condiciones escritas con SQL puro.
  • Se le reportaron pocas fallas de seguridad en relacion a los otros frameworks.
  • Al ser basado en Python y orientado a proceso en lugar de a petición, se puede implementar un caché "tonto" para los entornos de desarrollo sin obligarles a instalar un Memdached, hasta la etapa de producción o staging. De la misma forma, se permite hacer una única carga pesada de todo el esquema de clases (cosa que sería peligrosamente pesada si se hiciera petición por petición), lo que permite hacer un ORM sumamente poderoso.
No me gusta:
  • Algunos factores de diseño, como en las claves foráneas, son estéticamente inconsistentes.
  • En mi -no tan humilde- opinión, la comunidad parece ligeramente cerrada a cambios (aunque en realidad no me puse a compararla con las comunidades de otros frameworks).
  • Las migraciones "rompen" en algunas bases de datos como PostgreSQL si hacen cambios "verticales" (es decir: alterando tabla y columnas) y "horizontales" (es decir: alterando datos) en una misma tabla. PostgreSQL arroja un error, y no existe un mecanismo de dar a optar al usuario si quiere ejecutar cada migración en una transacción, o todas en una única transacción.
  • No tiene buen soporte para consultas como "select * from manager m where (select count(*) from employee where manager_id = m.id) > 5" mediante ORM sin recurrir a SQL puro.
  • El esquema de URLs es muy pesado. Habría sido ideal si, durante la carga, hubieran creado un esquema de URLs que fuera "compilable" (una única vez durante la carga del servidor), en lugar de obligar a cada petición a recorrer el complejo esquema de URLs, que es medio pesado (poderoso pero pesado).
  • De cuando en cuando es difícil obtener la petición actual, especialmente para propósitos de validación (ejemplo: yo mismo hice un componente en donde, para validar un campo, necesito acceder a la petición actualmente ejecutada), en contraste con otros frameworks como Flask.
  • Al ser WSGI es síncrono (esta desventaja la comparten todas las aplicaciones WSGI) y, aunque gunicorn (aplicación útil para desplegar cualquier cosa que sea WSGI) tiene un soporte para cosa asíncronas, no es lo más canónico y no siempre funciona bien. Determinadas aplicaciones no pueden realizarse, como chats o juegos en tiempo real, sin recurrir al long-polling o a soluciones algo inestables. Termino integrando otros frameworks como Tornado para esas tareas.
Conclusion

Espero que con este post hayas aprendido en qué sentido vale la pena relativizar una elección y en qué sentido no. Para los djangueros frecuentemente nos es dificil soltar Django, pero es útil que veamos también las desventajas y ver que soluciones podemos integrar. Cada vez más hostings están soportando Python, con un cómodo shell para usuarios (y cada vez son más baratos los VPS), por lo que implementar soluciones integradas es más fácil y no estamos obligados a atarnos a una bestia monolítica como suele ocurrir con PHP. Vale la pena que nos modernicemos y le demos oportunidades a muchas tecnologías para poder compararlas y elegir la mejor para nuestras necesidades, sin andar atándonos completamente a supuestas "modas" que nos impone el mercado. Lo importante no es poder elegir, sino saber elegir, y creo que eso siempre lo debemos tener en cuenta para el desarrollo, por mucho que nos guste una tecnología. Es importante elegir adecuadamente en relación a la plataforma y capacidades disponibles, pero también en relación a los recursos humanos y su productividad.

Tengo la esperanza de que, con el tiempo, más servicios de hosting van a soportar Python (hoy ya son muchos, igual), y más gente se va a introducir a este mundo de una manera no tan críptica sino más concentrada en el aspecto humano (y sus necesidades) de nuestra actividad.

miércoles, 6 de agosto de 2014

Nginx como proxy SSL y de Websockets.

Este manual indicará como instalar un nginx en Ubuntu.
El Ubuntu en cuestión es 13.04, mientras que el nginx en cuestión es 1.4.4.
Sin ser la versión más reciente, soporta WebSockets (nginx los soporta desde 1.3).

Instalando nginx

Para instalar nginx corremos:

$ sudo add-apt-repository ppa:nginx/stable
$ sudo apt-get update
$ sudo apt-get install nginx

Y esto nos instala así nomás el nginx. No deberíamos tener problemas, propios del nginx, durante la instalación.

Retirando al servidor web Apache de servicio

Luego tenemos que ver si tenemos apache instalado. Eso sería molesto porque en lugar de Apache nosotros queremos que el nginx ocupe sus puertos. A decir verdad Apache no es muy bueno que digamos, y tiende a ser reemplazado poco a poco. Nosotros vamos a hacer ese mismo reemplazo.

Primero veamos si lo tenemos instalado. Bastará con correr el siguiente comando:
$ apache2 -version
Si este comando falla indicando que no existe entonces omitimos esta sección, ya que no hay apache que retirar. En caso contrario tenemos dos opciones:
  • Desinstalar apache. Sea que lo hayas instalado con tasksel o con apt-get, para removerlo toca ejecutar:
    $ sudo apt-get remove apache2
  • Mantener instalado, pero retirar de entre los servicios que se ejecutan automáticamente. Esto ya que el nginx se instala como servicio, al igual que apache, y por lo tanto puede haber “colision” con los puertos que ocuparía cada uno en caso de correr en simultaneo. Lo normal es que querremos que nginx corra usando el puerto 80. Por esta razón, si queremos conservar apache pero quitarlo de servicio automático tenemos que hacer lo siguiente:
    $ sudo service apache2 stop
    $ sudo update-rc.d apache2 remove
    $ sudo update-rc.d nginx enable
    $ sudo service nginx start
    Esto debido a que la configuración de nginx fue creada durante la instalación pero nunca activada.
    Con esto ambos existen pero solo nginx se activará en adelante al encender la maquina.
Lo único que resta ahora, con nginx activo, es probar la configuración predeterminada que viene (localhost en puerto 80). Basta con que entremos a un navegador y probemos http://localhost/. Si aparece una página con título “It works!” y un cuerpo con texto, es que nuestro nginx está montado (lee bien lo que dice, no sea cosa que hayas omitido algo y aún leas el contenido de una página predeterminada de la instalación existente de Apache).

Una vez instalado exitosamente, vamos a cambiar la configuración predeterminada:
  • Dónde irán las configuraciones de nuestros sitios.
  • Cómo montaremos nuestro servidor para HTTP y HTTPS para Facebook.
  • Cómo montaremos nuestro servidor para Websockets.
Configuración de nuestros sitios: cómo y dónde

La configuración central del nginx se encuentra en /etc/nginx/nginx.conf. Para acceder a ella usamos:
$ sudo nano /etc/nginx/nginx.conf
Hay que recordar que es un archivo localizado en una zona “de administración” y que no nos pertenece, por lo que necesitamos acceder a él como administrador o no nos va a permitir guardarlo (es decir: se abriría en sólo lectura).

Lo que nos importa es editar dentro de la directiva de bloque “http”. En su interior vamos a buscar la siguiente línea:
include /etc/nginx/sites-enabled/*;
Esta línea tiene un significado: se incluye, como si fuera parte del archivo de configuración, todo el contenido que se encuentre en todos los archivos que “encajen” con este patrón. Es decir: que todo lo que se encuentre por debajo de esa rama de directorios será leído como configuración de nginx.

Si vamos hoy por hoy -así apenas lo instalamos- a ese directorio, lo que vamos a encontrar es un archivo llamado “default”. Ese archivo tiene una configuración para un servidor llamado “localhost” y que escucha por el puerto 80 en cualquier dirección IP que la máquina pueda ofrecer.

Nosotros podemos comentar esta línea, para no usarla, y en su lugar que use archivos que se encuentren en nuestra carpeta de usuario. Para esto abramos otra terminal y ejecutemos los siguientes comandos:
$ cd ~
$ mkdir nginx-confs
$ cd nginx
$ mkdir available
$ mkdir enabled
$ cd enabled
$ pwd
Con esto creamos unos directorios en nuestra carpeta personal: nginx-confs/available y nginx-confs/enabled. Lo que hacen esos directorios es guardar los sitios que están disponibles, y de entre ellos los sitios que están activados, para el nginx. El comando pwd nos dará el directorio que tendremos que incluir para tener los sitios. Ese directorio lo tenemos que copiar e incluir en la configuración de nginx:
#yo elijo, además, comentar la línea que incluye los directorios originales.
#de esta forma, solamente se incluyen los que son de mi carpeta personal.
#include /etc/nginx/sites-enabled/*;
include /home/miusuario/nginx-confs/enabled/*;
Y habiendo hecho esto, es hora de crear alguna configuración de prueba (la mía se llamará mirrorlings.dev):
touch ~/nginx-confs/available/mirrorlings.dev.conf
ln -s ~/nginx-confs/available/mirrorlings.dev.conf ~/nginx-confs/enabled/mirrorlings.dev.conf
Así la configuración estará disponible y activa (aunque por el momento no tiene nada).

Configurando un sitio HTTP/HTTPS

Lo normal de configurar un sitio que atienda ambas peticiones es que el sitio HTTP hace redirecciones al sitio HTTPS. Hay dos enfoques para hacer esto:
  • Un mismo servidor HTTP y HTTPS que haga todas las redirecciones cuando vea que el esquema es http://.
  • Un servidor HTTPS que contenga el sitio y uno HTTP que redirija al sitio HTTPS todas las peticiones que reciba.
Nosotros vamos a tomar el segundo enfoque, por lo que vamos a crear dos configuraciones separadas.

Para el sitio HTTPS tenemos que crear el par de claves. La encriptación que se realiza con HTTPS es encriptación asimétrica para transmisión de secreto, y luego encriptación simétrica usando el secreto transmitido. Para esto tenemos que hacer un “par de claves”. Creando un “par de claves” la vamos a pasar bien para nuestro servidor de desarrollo pero no para un servidor en producción porque:
  1. El servidor de producción generalmente lo contrataremos o, al menos, contrataremos el dominio para dicho servidor, y el locador del servicio será quien nos provea estos archivos necesarios para nuestro sitio.
  2. Estos certificados no estarán firmados por ninguna autoridad de confianza y, al ingresar un usuario al sitio mediante navegador, un mensaje de advertencia indicando que “este sitio no es de confianza” será mostrado al usuario. Esto como desarrolladores no ofrece problema alguno (es cuestión de tocar un botón) pero sí al momento de mostrar el sitio al público.
En Ubuntu 13.04 la utilidad openssl viene instalada previamente, y nos servirá para crear estos certificados “auto-firmados”.

Primero, vamos a crear algún directorio para nuestras claves:
$ cd ~/nginx-confs
$ mkdir ssl
$ cd ssl
Y a partir de ahi, vamos a crear las claves:
  1. Creación de clave privada (el archivo contiene, en realidad, el par de claves).
    $ openssl genrsa -des3 -out mirrorlings.dev.key 2048
    Este comando creará una clave privada RSA con un módulo de 2048 bits. El término módulo hace referencia al algebra modular y tiene exclusiva relación con el algoritmo RSA. Lo importante es que, de una u otra forma, mientras más larga una clave utilizada en un algoritmo de encriptación, más difícil es destruir el sistema de encriptación e interceptar las comunicaciones. Hoy el número 2048 es bastante aceptable como longitud de clave.

    El archivo que contiene la clave estará así mismo encriptado mediante un algoritmo simétrico. Este algoritmo simétrico (llamado DES3) requiere una clave para encriptar y desencriptar (es la misma clave). Cuando nosotros enviemos ese comando por la consola, se nos solicitará que ingresemos (y repitamos) una contraseña. No hay que olvidar esa contraseña ya que sino no podremos hacer uso de la clave, y tendremos que borrarla y comenzar nuevamente desde este punto.

  2. Creación de solicitud de firma de certificado.

    Para poder crear un certificado -que sirve para que la clave pública pueda utilizarse para certificar la comunicación con el servidor que tendrá la clave privada- tenemos que crear un “certificate signing request” que es un archivo que contendrá sólamente la clave pública, y que una “autoridad” debe consentir en decir que esa clave es del sitio del que clama ser. A partir de esta solicitud crearemos luego el certificado. Para crearla, debemos ejecutar el comando:
    $ openssl req -new -key mirrorlings.dev.key -out mirrorlings.dev.csr
    Nos solicitará la contraseña que ingresamos en el punto anterior al crear el archivo que tiene la clave privada. Debemos ingresarla o no podremos continuar.

    Nota: este comando solicitará muchos datos que no son relevantes, ya que es una clave de desarrollo. Excepto cuando solicite el Common Name. Ahí tendríamos que poner mirrorlings.dev, ya que es el nombre completo del sitio que usaré.
  1. Remoción de la contraseña de la clave privada.

    Es una cuestión de seguridad que, siempre que operemos sobre una clave privada, nos pida la contraseña. Esto impide que otras personas se metan y accedan a ella. Lo que es molesto es que sea el servidor, en cada oportunidad en que se reinicie, quien pida la clave. Por lo tanto vamos a tomar dos medidas:


    1. Creamos una copia desencriptada de la clave:
      $ cp mirrorlings.dev.key mirrorlings.dev.key.2 
      $ openssl rsa -in mirrorlings.dev.key.2 -out mirrorlings.dev.key$ rm mirrorlings.dev.key.2
      Lo primero en lo que consiste es en que copiamos la clave a un archivo temporal.
      De ese archivo temporal tomamos la clave de entrada, y la guardamos sobreescribiendo el archivo de clave original. Como la clave está encriptada, tenemos que ingresar por última vez la contraseña que elegimos en el punto 1.
      Finalmente, en nuestro archivo mirrorlings.dev.key tendremos la misma clave que antes, pero desencriptada, por lo que deberíamos borrar el archivo mirrorlings.dev.key.2, cuyo propósito era temporal y ya no nos es necesario.

    2. Modificaremos los permisos para que nadie pueda leer ni escribir sobre ese archivo de clave (ni siquiera el usuario):
      $ chmod 0000 mirrorlings.dev.key
      Ya removido eso, sabremos que si algún comando ejecutado quiere operar sobre la clave (sea un comando de openssl o el mismo nginx) entonces debe ser un comando ejecutado como superusuario. Por fortuna, para nosotros, nginx es ejecutado con privilegios de superusuario. Si algo falla con los permisos posteriormente, tendremos que cambiar los permisos de 0000 a 0400 (sólo lectura, sólo el usuario creador).
  1. Creamos el certificado “firmando” la solicitud anterior. Lo normal es que este comando lo hace una entidad autorizante, pero como esta es una clave para desarrolladores lo haremos nosotros, y tendremos un certificado firmado por nosotros mismos (de ahí el término “auto firmado”: utilizará la misma clave privada para firmarse). Lo hacemos con el siguiente comando:
    $ openssl x509 -req -days 10000 -in mirrorlings.dev.csr -signkey mirrorlings.dev.key -out mirrorlings.dev.crt
    Con esto, tendremos un certificado x509 para nuestra clave privada, que será firmado con la misma clave privada, y que producirá un certificado auto-firmado listo para usarse durante 10000 días (casi 30 años).

    Este certificado así creado nos servirá para un solo dominio en SSL. Si queremos soportar múltiples dominios tenemos que tener:
    1. Un dominio base puesto en nuestro CN al momento de crear la solicitud de certificado en el punto 2. Para esto puedo dejar el mismo mirrorlings.dev.
    2. Creamos una extensión de OpenSSL para el certificado, que tenga una de las dos siguientes:
      • Una lista de subdominios de mirrorlings.dev. Nuestro archivo (sea de ejemplo mirrorlings.dev.ssl.ext) debería contener algo como:

        [ mis_subdominios_para_mirrorlings_punto_dev ]
        subjectAltName = @aca_tengo_mis_dns [ aca_tengo_mis_dns ] DNS.1 = www.mydomain.com DNS.2 = nexus.mydomain.com DNS.3 = trac.mydomain.com DNS.4 = svn.mydomain.com
      • Un wildcard de subdominios de mirrorlings.dev. Como ventaja es que se pueden agregar multiples sitios usando misma clave. Como desventaja es que necesitamos una clave bien resistente y cara (MUY cara, por si la anterior ya no lo era) al momento de pasar a producción: [ mis_subdominios_para_mirrorlings_punto_dev ] subjectAltName = DNS:*.mirrorlings.dev
    3. Finalmente, necesitamos el comando apropiado para generar el certificado: $ openssl x509 -req -days 10000 -in mirrorlings.dev.csr -signkey mirrorlings.dev.key -out mirrorlings.dev.crt -extfile mirrorlings.dev.ssl.ext -extensions mis_subdominios_para_mirrorlings_punto_dev
Y ya tendríamos dos archivos creados en este directorio: mirrorlings.dev.crt (certificado) y mirrorlings.dev.key (clave). Vamos a configurar un servidor HTTPS con las características mencionadas antes, y los archivos de clave y certificados creados ahora. Por lo tanto nos moveremos al directorio ../available, donde tenemos las configuraciones de nuestros servidores, y modificamos aquel archivo que ya tenemos por ahí vacío esperando una configuración:
$ cd ../available
$ nano mirrorlings.dev.conf
Y le ponemos este contenido:
server {
    listen 443 default ssl;
    server_name mirrorlings.dev;
    ssl_certificate /home/miusuario/nginx-confs/ssl/mirrorlings.dev.crt;
    ssl_certificate_key /home/miusuario/nginx-confs/ssl/mirrorlings.dev.key; 
    location / {
        root /home/miusuario/prueba;
    }
}
server {
    listen 80;
    server_name mirrorlings.dev;
    rewrite ^ https://$server_name$request_uri? permanent;
    #o mejor aun: return 301 https://$host$request_uri?;
}
Como se puede ver, tenemos dos configuraciones:
  1. La de puerto 80 (insegura) redirige todo su trafico a la de puerto 443 (segura), haciendo que el navegador la cargue automática y explícitamente en sí mismo.
  2. La de puerto 443 (segura) cargará, de momento, sólamente archivos estáticos en algún directorio. Luego le pondremos la configuración que debe ir.
Todo este tiempo usamos un dominio inexistente llamado “mirrorlings.dev” como nombre de servidor. Este dominio no existe, no existirá nunca, y no tiene manera alguna de resolverse por las vías normales. Como es de prueba únicamente, debe existir (lo necesitaremos para hacer la request ya que nginx buscará qué servidor atiende la request por encaje de dominio, y como usaremos mirrorlings.dev de nombre de servidor, ese dominio debe poder resolverse de alguna manera), y únicamente para nosotros, por lo que tenemos que crear una entrada para este dominio de manera local:
$ sudo nano /etc/hosts
Una vez dentro, agregamos una entrada:
127.0.0.1 mirrorlings.dev
La configuración que existe es una configuración de ejemplo para ver que todo vaya bien. Para probarla vamos a tener que hacer 3 cosas:
  • Crear ese directorio de prueba:
    $ cd ~
    $ mkdir prueba
  • Crear un archivo en su interior:
    $ cd prueba
    $ nano hola.txt
    hello world
  • Reiniciando las configuraciones del servidor para cargar la nuestra:
    $ sudo nginx -s reload
  • Intentando acceder al archivo hola.txt ingresando a http://mirrorlings.dev/hola.txt. Si todo va bien, deberíamos ver la advertencia de nuestro navegador de que “este sitio no es seguro” y, tras ingresar de todos modos, ver el contenido “hello world”.
Con esto, deberíamos tener nuestro sitio funcionando.

Cómo convertir nuestro servidor en un proxy HTTP y de Websockets con socket.io

Hasta ahora nuestro sitio sólamente lee archivos estáticos en cierto directorio especificado como “root” para nuestra configuración de servidor. Deberíamos hacer que nuestro servidor sea capaz de procesar peticiones, en lugar de simplemente servir archivos.

Esto es particularmente interesante si nuestro servidor soporta comunicación en tiempo real con Websockets (¿Cuál sería sino el propósito de este tutorial?). Entonces vamos a tomar algunas precondiciones, por lo que esta parte es conveniente leerla únicamente cuando hayamos montado un proyecto de django con websockets:
  • Se sabe programar en Django. Este tutorial no va a explicar cómo montar un sitio en Django.En algún momento hago uno de Django pero, sinceramente, la documentación oficial es más que buena, por lo que recomiendo leerla y el tutorial que yo haré asumirá que se tiene un conocimiento básico de cómo funciona una aplicación Django (fácil, ni siquiera requerirá que el usuario sepa configurar la aplicación django.contrib.admin o crear un modelo).
  • Se tiene un proyecto en Django funcionando, con soporte para websockets usando el paquete gevent-socketio y montado con gunicorn. Esto mismo explicaré en otro tutorial.
Nuestra configuración actual de servidor muestra únicamente archivos estáticos. Vamos a cambiar eso:
$ nano mirrorlings.dev
En su interior reemplazamos la directiva root por una que nos permita dirigirle todo el tráfico a nuestra aplicación Django/websockets. Como ejemplo diremos que la tenemos en nuestro mismo servidor, escuchando por la dirección 127.0.0.1 y por el puerto 8800. Estos puertos pueden ser cualesquiera en tanto sean mayores a 1024, o tendremos un problema ya que para esos puertos deberíamos tener privilegios de administrador.

Buscamos donde tenemos:
location / {
    root /home/miusuario/prueba;
}
Y tras borrarlo ponemos lo siguiente:
location / {
    proxy_pass http://127.0.0.1:8800;
}
location /static/ {
    root /home/miusuario/donde/tengo/mi/proyecto;
}
Asumiendo que nuestro STATIC_ROOT resuelve contra exactamente el directorio puesto como root, y nuestro STATIC_URL resuelve a “/static/”. Algo parecido deberíamos hacer con /media/ si es que usamos archivos cargados por el usuario.

Nuevamente salvamos este archivo de configuración, ejecutamos nuestro servidor django con gunicorn, y reiniciamos nginx:
$ sudo nginx -s reload
Y deberíamos ver nuestro sitio funcionando tanto en http como en https. Cabe recordar que a django le llegarán todas las peticiones que vienen de la etapa de https, sin embargo nuestra aplicación django las recibirá como http.

Con esto ya tenemos nuestra aplicación andando. Hay que tener cuidado porque la IP de origen que consultemos en nuestra aplicación será siempre la dirección local, porque proviene del nginx. Debería nuestra aplicación django tener algún middleware que rescate la IP actual que estará en la “cadena de IPs” que se forma normalmente en encabezados como X-Forwarded-For o X-Real-IP.

Cómo convertir nuestro servidor en un proxy HTTP y de Websockets con django-gevent-websocket

En realidad el paquete socket.io puede ser muy cabrón y hacer -de forma predeterminada- uso de long-polling en lugar de usar WebSockets. En realidad no sé del todo que le pasa a esa implementación en python, por lo que probé django-gevent-websocket, que salió hace pocos meses.

En esta implementación (veré si yo mismo puedo trabajar en ella) se montan dos servidores: uno para http normal y uno para los websockets. Lo normal es que hacemos salir solamente un servidor a internet, por un solo puerto. Por este motivo vamos a hacer uso nuevamente de nginx. Nuestra configuración va a ser, en vez de la anterior:
server {
    listen              443 default ssl;
    #RECUERDEN IMPEDIR CUALQUIER VERSION DE SSL - LIMITENSE A USAR TLS
    server_name         mirrorlings.dev;
    ssl_certificate     /home/miusuario/nginx-confs/ssl/mirrorlings.dev.crt;
    ssl_certificate_key /home/miusuario/nginx-confs/ssl/mirrorlings.dev.key;
    location /static/ {
        root /home/miusuario/Proyectos/facebook-realtime;
    }
    if ($http_upgrade) {
        rewrite ^(.*)$ /__ws__/$1 break;
    }
    location / {
        proxy_pass http://127.0.0.1:8800;
    }
    location /__ws__/ {
        proxy_pass http://127.0.0.1:8801;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        rewrite ^/__ws__/(.*)$ $1 break;
    }
}
server {
    listen      80;
    server_name mirrorlings.dev;
    rewrite     ^   https://$server_name$request_uri? permanent;
}
Esto requiere que montemos los dos servidores con GUnicorn: el http sobre el puerto 8800 -es solamente un ejemplo- usando como worker class a "gevent"; el de websockets sobre el puerto 8801 -otro ejemplo; vale el puerto que quieras y puedas- usando como worker la clase "geventwebsocket.gunicorn.workers.GeventWebSocketWorker".

El comando para reiniciar nginx es el mismo:
$ sudo nginx -s reload
Y con esto tenemos nuestro servidor montado y funcionando. Nuestras peticiones de websockets a wss://mirrorlings.dev/alguna/vista deberían funcionar. No hay que usar el protocolo ws:// ya que ese va por http y, al sufrir una redirección a https, la conexión falla.

Si te quedó alguna duda al respecto, no dudes en comentar. Si se del tema, tal vez te pueda dar una mano.