Desarrollo
La historia de un request HTTP en Rails
Llegar a un sitio es simple: escribes una URL en el navegador, por ejemplo «https://www.mydomain.com», y en un par de segundos aparece una página, un video, una imagen o lo que esa petición traiga. Para quien no programa, es magia. Para un desarrollador web, vale la pena conocer la historia real de un request y lo que pasa detrás del telón.
Para no alargar el cuento, lo explico desde un punto de vista superficial: un request HTTP básico a un endpoint de una app Rails. El agujero de conejo puede verse intimidante, pero no tiene por qué serlo.
Para empezar hay que entender HTTP. ¿Has visto un error 404 o 500? Seguro más de una vez. HTTP es el HyperText Transfer Protocol: permite que el navegador y el servidor web se hablen. El principio es request y response.
Cuando escribes una URL o haces clic en un enlace, el navegador genera un request. Cada request lleva headers con instrucciones para que el servidor responda acorde. Vamos a analizar un CURL a mi perfil de GitHub para ver las partes importantes.
El request
THE REQUEST
najera % curl https://github.com/JNajera --verbose
* Trying 140.82.113.3...
* TCP_NODELAY set
* Connected to github.com (140.82.113.3) port 443 (#0)
* SSL HANDSHAKE A TOPIC FOR ANOTHER POST
* SSL certificate verify ok.
> GET /JNajera HTTP/1.1
> Host: github.com
> User-Agent: curl/7.64.1
> Accept: */*
... RESPONSE CONTINUED ...
Para hablar con el servidor, el navegador primero hace un DNS lookup y encuentra dónde está hospedado github.com: la IP del servidor que puede responder. Busca primero en caché local; si no está, sale a internet y consulta los DNS raíz. Este tema daría para otro post; lo dejamos corto.
Cuando el navegador tiene la IP, prepara el request y lo entrega. En la primera línea está el verbo HTTP —en el ejemplo, GET, que suele pedir un documento, imagen, video, etc.—, luego la URL pedida «/JNajera» y la versión HTTP, aquí 1.1.
Las otras líneas aportan extra: el host, el tipo de dato que se espera (JSON o texto plano, por ejemplo) y un montón de headers que pueden hacer el request más específico.
Generar una respuesta dentro de una app Rails
Cuando el request llega al servidor, pasan varias cosas según el setup. Imaginemos que github.com es una app Rails en un VPS de un solo servidor.
El request suele ir al «web server», luego al middleware de Rack y al final a Rails. Es una montaña rusa: hay que pasar por todo para que la respuesta vuelva al navegador.
Un «web server» solo «habla» HTTP. Hay varios: Phusion Passenger, Puma, Unicorn o Webrick (por favor, no lo uses en producción). Algunos están escritos en C, otros en Ruby. Su trabajo básico es entender el request y decidir qué hacer: servir una imagen de assets o lo que esté configurado para ciertos casos.
El «web server» pasa la información a Rack. Rack es una API simple entre Rails y el web server: le dice a Rails «tengo un request, aquí van verbo, URL, headers» y traduce el resultado de Rails al web server con status, headers y body. La razón de Rack es poder enchufar cualquier web server compatible y que la app siga corriendo igual.
Rails usa middleware de Rack para cookies, logs, profiling, caché y otras piezas. Rack parece una caja negra… no debería. Puedes listar todo el middleware que actúa antes de que el request llegue a la app.
najera % bin/rails middleware
use Rack::Sendfile
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Callbacks
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
run Github::Application.routes
Después de Rack y de todo el middleware, hay luz: la app Rails toma el control y responde. El último paso de Rack es pasar los datos al router. Aquí conviene hablar de los verbos HTTP. ¿Cuáles existen y por qué importan en Rails?
Los verbos básicos que un desarrollador debe entender son GET, POST, PUT, DELETE y PATCH. En el mundo Rails o de APIs REST, el verbo y la URL ayudan a encontrar el controller y la action. Por convención cada action mapea a una operación CRUD (Create, Retrieve, Update, Delete). Se puede violar esa guía, pero no es buena idea.
Cuando el request entra a Rails, se compara con routes.rb, que mapea un verbo y una URL a un controller#action. El trabajo del router es decidir a dónde va el request.
Al final, se ejecuta tu código en ese controller y action. Sigamos el ejemplo del CURL a mi perfil de GitHub: ¿qué debería pasar?
La app busca un «User» en la base con el slug de la URL «/JNajera». Si coincide, regresa el registro y renderiza HTML. Aquí entra el MVC, pero eso es tema de otro post: por ahora lo dejamos como caja negra.
La respuesta
THE REQUEST
najera % curl https://github.com/JNajera --verbose
... REQUEST HEADERS ...
THE RESPONSE
< HTTP/1.1 200 OK
< server: GitHub.com
< date: Wed, 27 May 2020 03:52:23 GMT
< content-type: text/html; charset=utf-8
< status: 200 OK
< vary: X-Requested-With, Accept-Encoding, Accept, X-Requested-With
< etag: W/"b1ea90f48557b7d5c47e218d04661775"
< cache-control: max-age=0, private, must-revalidate
< strict-transport-security: max-age=31536000; includeSubdomains; preload
< HERE MORE DATA HAS BEEN OMITTED
No te rindas, ya casi. Cuando el request termina su ciclo en la app y la respuesta vuelve a Rack, de Rack al web server y del web server al navegador, el browser lee esos datos para pintar lo que el servidor preparó.
En el ejemplo, la primera línea de la respuesta tiene el protocolo HTTP 1.1 y el status 200 OK (¡sí! asumimos que encontró mi perfil). Hay varios códigos de status según lo que haya pasado en el viaje. Se agrupan en cinco clases:
1. Informational responses (100–199)
2. Successful responses (200–299)
3. Redirects (300–399)
4. Client errors (400–499)
5. and Server errors (500–599)
Un 200 OK significa que el request volvió a casa sano y completo. Después vienen más headers con el detalle de cómo se manejó. Al final está el body: HTML, CSS, JavaScript u otro dato binario (imagen, PDF, texto).
La historia de un request es un viaje que dura menos que un parpadeo, y eso emociona. La próxima vez que arranques un «Hello World» en Rails, piensa en todo el trabajo que Rails hace por ti para que tú te concentres en crear la aplicación.