Rack

De Rack me interesa por ahora

Rack es un tipo de middleware. Se ubica entre su aplicación web y el servidor web. Maneja todas las llamadas API específicas al servidor, pasa la solicitud HTTP y todos los parámetros del entorno en un hash, y entrega la respuesta de su aplicación de vuelta al servidor. En otras palabras, su aplicación no necesita saber cómo hablar con el servidor HTTP, necesita saber cómo hablar con Rack.

Esto tiene una serie de ventajas. Primero, hablar con Rack es fácil. Segundo, ya que sólo necesita hablar con Rack, y Rack sabe como hablar con diferentes servidores HTTP, su aplicación correrá en cualquiera de dichos servidores HTTP. Rack es como un adaptador universal para aplicaciones web.

Las propias aplicaciones Rack no son nada especial en sí mismas, de hecho el API Rack es muy simple, puede ser descrito con una oración:

Una aplicación Rack es cualquier objeto Ruby que responda al método call, tome un hash como parámetro y devuelva un array con el código de salida de estado, los encabezados de respuesta y el cuerpo de la respuesta como un arreglo de strings.

¿Por qué un programador debería interesarse en Rack? Primero en general siempre hay inspiración en el comprender cómo su marco de desarrollo funciona, pero más importante es que hay muchas cosas útiles que hace con Rack, resaltando, middleware.

El middleware Rack es un capa extra entre su applicación y Rack. Lo que hacen los middleware Rack es tomar la solicitud de Rack, pasarla a su aplicación, tomar su respuesta, agregar algo a ella o filtra algo en el camino y luego pasar la respuesta de vuelta a Rack. Esto se puede usar para implementar cada pequeña funcionalida interesante como un logger agnóstico (independiente del servidor), o un verificador de solicitud sana, o que se envíe un email cada vez que su aplicación devuelva un 404 a los sysadmin. Ninguna de estas funcionalidades enredan su aplicación, pueden ser implementadas como middleware con Rack.

Hola Mundo con Rack

Primero comencemos con el tradicional "Hola Mundo". Esta aplicación, sin importar el tipo de solicitud, devolverá el código de estado 200 (en idioma HTTP , "ok") y la cadena "Hola Mundo" como el cuerpo.

Antes de examinar el código, considere nuevamente los requerimientos que cualquier aplicación Rack debe cumplir.

Una aplicación Rack es cualquier objeto Ruby que responda al método call, tome un hash como parámetro y devuelva un array con el código de respuesta, los encabezados HTTP y el cuerpo de la respuesta como arreglo de cadenas.

1    class HolaMundo
2      def call(env)
3        return [200, {}, ["¡Hola Mundo!"]]
4      end
5    end

Como puede ver, cualquier objeto de la clase HolaMundo cumple los requerimientos. Lo hace de forma mínima y no realmente muy útil, pero cumple todos los requerimientos.

Bien simple, ahora "enchufemos" esto en WEBrick (el servidor HTTP que viene con el propio Ruby). Para hacerlo utilizamos el método Rack::Handler::WWEBrick.run, le pasamos una instancia de HolaMundo y el puerto en el cual ejecutar. Un servidor WEBrick debería entonces ejecutarse y Rack pasando las solicitudes entre las solicitudes HTTP y su aplicación.

Nota: esto no es la forma ideal de lanzar aplicaciones con Rack, sólo lo estamos haciendo de forma ilustrativa antes de introducir la funcionalidad Rack llamada Rackup.

El usar Using Rack::Handler de esta forma tiene varios problemas. Primero no es muy configurable, todo está cableado en el script. Segundo, no podrá hacerle kill al programa, no responderá a Ctrl-C. Si corre este programa, simplemente cierre el terminal y abra uno nuevo.

hola_mundo_1.rb

 1    #!/usr/bin/env ruby
 2    require 'rubygems'
 3    require 'rack'
 4
 5    class HolaMundo
 6      def call(env)
 7        return [200, {}, ["¡Hola Mundo!"]]
 8      end
 9    end
10
11    Rack::Handler::WEBrick.run(
12      HolaMundo.new,
13      :Port => 9000
14    )

$ ./hola_mundo_1.rb 
[2011-12-28 06:47:19] INFO  WEBrick 1.3.1
[2011-12-28 06:47:19] INFO  ruby 1.8.7 (2011-06-30) [x86_64-linux]
[2011-12-28 06:47:19] WARN  TCPServer Error: Address already in use - bind(2)
[2011-12-28 06:47:19] INFO  WEBrick::HTTPServer#start: pid=19453 port=9000
movix.fedora-ve.org - - [28/Dec/2011:06:47:58 VET] "GET / HTTP/1.1" 200 13
- -> /
...
$ curl http://localhost:9000
¡Hola Mundo!

Si bien lo anterior es bastante fácil de hacer, no es la forma en que normalmente se usa Rack. Rack es normalmente utilizado con una herramienta llamada rackup. Rackup hace más o menos lo de conectar con el servidor en nuestro script previo, pero en una forma más utilizable. Rackup se ejecuta desde la línea de comandos y se le pasa un archivo .ru o "archivo Rackup". Dicho archivo es simplemente un script Ruby que, entre otras cosas, alimenta a Rackup con su aplicación.

Un archivo Rackup muy simple para el ejemplo anterior luciría así:

hola_mundo_1.ru

 1    class HolaMundo
 2      def call(env)
 3        return [
 4          200,
 5          {'Content-Type' => 'text/html'},
 6          ["¡Hola Mundo!"]
 7        ]
 8      end
 9    end
10
11    run HolaMundo.new

Primero, hay que hacer un pequeño cambio en nuestra clase HolaMundo. Rackup usa un middleware llamado Rack::Lint para verificar que las respuestas sean sanas. Todas las respuestas HTTP deben tener un encabezado Content-Type, entonces lo hemos agregado. Luego la última línea del archivo Rackup crea una instancia de la aplicación y se la pasa al método run. Idealmente su aplicación no debería ser escrita enteramente dentro del archivo Rackup, se debe requerir su aplicación en él y crear una instancia de ella de esa forma. El archivo Rackup es solamente una pega, no debe tener código de la aplicación.

Si ejecuta el comando rackup hola_mundo_1.ru, se iniciará un servidor en el puerto 9292, ése es el puerto por omisión de Rackup.

$ rackup hola_mundo_1.ru
127.0.0.1 - - [28/Dec/2011 06:57:07] "GET / HTTP/1.1" 200 - 0.0011
127.0.0.1 - - [28/Dec/2011 06:57:13] "GET / HTTP/1.1" 200 - 0.0010
...
$ curl http://localhost:9292
¡Hola Mundo![gomix@movix Rack]$ curl http://localhost:9292 -v
* About to connect() to localhost port 9292 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.21.7 (x86_64-redhat-linux-gnu) libcurl/7.21.7 NSS/3.12.10.0 zlib/1.2.5 libidn/1.22 libssh2/1.2.7
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Connection: close
< Date: Wed, 28 Dec 2011 11:27:13 GMT
< Transfer-Encoding: chunked
< Content-Type: text/html
< 
* Closing connection #0

Rackup tiene funcionalidades útiles adicionales, puede cambiar el puerto en la línea de comandos, o en una línea especial en el script. En la línea de comandos simplemente pase el parámetro -p puerto. Por ejemplo, rackup -p 1337 hola_mundo_1.ru.

En el propio script, si la primera línea comienza con #\, entonces es interpretada como la línea de comandos. Así entonces puede definir las opciones allí. Si desea correr su aplicación en el puerto 1337, la primera línea del archivo Rackup sería:

1#\ -p 1337.