Plug vive en el corazón de la capa HTTP de Phoenix. Interactuamos con los enchufes o Plug en cada paso del ciclo de vida de una request, y los componentes principales de Phoenix, como end points, enrutadores y controladores, son solo enchufes internamente.
Plug es una especificación para módulos componibles entre aplicaciones web. También es una capa de abstracción para adaptadores de conexión de diferentes servidores web. La idea básica de Plug es unificar el concepto de "conexión" sobre la que operamos. Esto difiere de otras capas de middleware HTTP como Rack, donde la solicitud y la respuesta están separadas en la pila de middleware.
En el nivel más simple, la especificación Plug viene en dos formas: complementos de funciones y complementos de módulos.
Para actuar como un enchufe, una función necesita: aceptar una estructura de conexión (%Plug.Conn{}) como primer argumento y opciones de conexión como segundo;
Cualquier función que cumpla con estos dos criterios servirá. Por ejemplo:
def introspect(conn, _opts) do
IO.puts """
Verb: #{inspect(conn.method)}
Host: #{inspect(conn.host)}
Headers: #{inspect(conn.req_headers)}
"""
conn
end
Esta función hace lo siguiente:
- Recibe una conexión y opciones (que no usamos)
- Imprime alguna información de conexión en el terminal.
- Devuelve la conexión
Veamos esta función en acción agregándola a nuestro end point en lib/hello_web/endpoint.ex. Podemos conectarlo en cualquier lugar, así que hagámoslo insertando plug :introspect justo antes de delegar la solicitud al enrutador:
defmodule HelloWeb.Endpoint do
...
plug :introspect
plug HelloWeb.Router
def introspect(conn, _opts) do
IO.puts """
Verb: #{inspect(conn.method)}
Host: #{inspect(conn.host)}
Headers: #{inspect(conn.req_headers)}
"""
conn
end
end
Los complementos de función se conectan pasando el nombre de la función como un átomo. Para probar esto, vamos al navegador y buscamos http://localhost:4000. Debería ver algo como esto impreso en su terminal de shell:
Verb: "GET"
Host: "localhost"
Headers: [...]
Nuestro enchufe simplemente imprime información de la conexión. Aunque nuestro complemento inicial es muy simple, puede hacer prácticamente cualquier cosa que desee dentro de él.
Ahora veamos la otra variante de enchufe, los enchufes de módulo.
Los conectores de módulo son otro tipo de conector que nos permite definir una transformación de conexión en un módulo. El módulo solo necesita implementar dos funciones:
- init/1 que inicializa cualquier argumento u opción que se pase a call/2
- call/2 que realiza la transformación de la conexión. call/2 es solo un complemento de función como vimos anteriormente
Para ver esto en acción, escribamos un complemento de módulo que coloque la clave y el valor :locale en la conexión para uso posterior en otros complementos, acciones de controlador y nuestras vistas. Coloquemos el contenido a continuación en un archivo llamado lib/hello_web/plugs/locale.ex:
defmodule HelloWeb.Plugs.Locale do
import Plug.Conn
@locales ["en", "fr", "de"]
def init(default), do: default
def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do
assign(conn, :locale, loc)
end
def call(conn, default) do
assign(conn, :locale, default)
end
end
Para probarlo, agreguemos este complemento de módulo a nuestro enrutador, agregando el complemento HelloWeb.Plugs.Locale, "en" a nuestra tubería de navegación en lib/hello_web/router.ex:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug HelloWeb.Plugs.Locale, "en"
end
...
En la devolución de llamada init/1, pasamos una configuración regional predeterminada para usar si no hay ninguna presente en los parámetros. También utilizamos la coincidencia de patrones para definir varios encabezados de función call/2 para validar la configuración regional en los parámetros, y recurrimos a "en" si no hay ninguna coincidencia. assign/3 es parte del módulo Plug.Conn y es cómo almacenamos valores en la estructura de datos de conn.
Para ver la asignación en acción, vayamos a la plantilla en lib/hello_web/components/layouts/home.html.heex y agreguamos el siguiente código después del cierre de la etiqueta </h1>:
<p>Locale: <%= @locale %></p>
Si vamos a http://localhost:4000/ y debería ver la configuración regional exhibida. Y si vamos a http://localhost:4000/?locale=fr y debería ver la asignación cambiada a "fr". Alguien puede usar esta información junto con Gettext para proporcionar una aplicación web totalmente internacionalizada.
Eso es todo lo que hay que hacer con Plug. Phoenix adopta el diseño de enchufe de las transformaciones componibles en toda la pila.