Seguimos la idea del
post anterior: Phoenix adopta el dise帽o de enchufe o plugs para funcionalidad componible.
Por ejemplo los Endpoints estan organizados por plugs:
defmodule HelloWeb.Endpoint do
...
plug :introspect
plug HelloWeb.Router
Los conectores Endpoint predeterminados hacen bastante trabajo. Aqu铆 est谩n en orden:
Plug.Static: sirve activos est谩ticos. Dado que este complemento viene antes que el registrador, las solicitudes de activos est谩ticos no se registran.
Phoenix.LiveDashboard.RequestLogger: configura el registrador de solicitudes para Phoenix LiveDashboard, esto le permitir谩 tener la opci贸n de pasar un par谩metro de consulta para transmitir registros de solicitudes o habilitar/deshabilitar una cookie que transmite registros de solicitudes desde su tablero.
Plug.RequestId: genera un ID de solicitud 煤nico para cada solicitud.
Plug.Telemetry: agrega puntos de instrumentaci贸n para que Phoenix pueda registrar la ruta de la solicitud, el c贸digo de estado y la hora de la solicitud de forma predeterminada.
Plug.Parsers: analiza el cuerpo de la solicitud cuando hay disponible un analizador conocido. De forma predeterminada, este complemento puede manejar contenido codificado en URL, de varias partes y JSON (con Jason). El cuerpo de la solicitud se deja intacto si el tipo de contenido de la solicitud no se puede analizar.
Plug.MethodOverride: convierte el m茅todo de solicitud en PUT, PATCH o DELETE para solicitudes POST con un par谩metro _method v谩lido.
Plug.Head: convierte las solicitudes HEAD en solicitudes GET y elimina el cuerpo de la respuesta
Plug.Session: un complemento que configura la gesti贸n de sesiones. Tenga en cuenta que fetch_session/2 a煤n debe llamarse expl铆citamente antes de usar la sesi贸n, ya que este complemento solo configura c贸mo se obtiene la sesi贸n.
En el medio del endpoint, tambi茅n hay un bloque condicional:
if code_reloading? do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello
end
Este bloque solo se ejecuta en desarrollo. Permite:
- recarga en vivo: si cambia un archivo CSS, se actualizan en el navegador sin actualizar la p谩gina;
- recarga de c贸digo, para que podamos ver los cambios en nuestra aplicaci贸n sin reiniciar el servidor;
- verificar el estado del repositorio, lo que garantiza que nuestra base de datos est茅 actualizada y, de lo contrario, genera un error legible y procesable.
En el enrutador, podemos declarar enchufes dentro de tuber铆as:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {HelloWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug HelloWeb.Plugs.Locale, "en"
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :index
end
Las rutas se definen dentro de los 谩mbitos y los 谩mbitos pueden canalizarse a trav茅s de m煤ltiples canalizaciones. Una vez que una ruta coincide, Phoenix invoca todos los complementos definidos en todas las tuber铆as asociadas a esa ruta. Por ejemplo, acceder a "/" se canalizar谩 a trav茅s de la canalizaci贸n del navegador, invocando en consecuencia todos sus conectores.
Como veremos en la gu铆a de enrutamiento, las tuber铆as en s铆 mismas son tapones. All铆, tambi茅n discutiremos todos los complementos en la tuber铆a :browser.
Finalmente, los controladores tambi茅n son enchufes, por lo que podemos hacer:
defmodule HelloWeb.PageController do
use HelloWeb, :controller
plug HelloWeb.Plugs.Locale, "en"
En particular, los complementos del controlador brindan una funci贸n que nos permite ejecutar complementos solo dentro de ciertas acciones. Por ejemplo, puedes hacer:
defmodule HelloWeb.PageController do
use HelloWeb, :controller
plug HelloWeb.Plugs.Locale, "en" when action in [:index]
Y el complemento solo se ejecutar谩 para la acci贸n de 铆ndice.
Al cumplir con el contrato de conexi贸n, convertimos una solicitud de aplicaci贸n en una serie de transformaciones expl铆citas. No se detiene ah铆. Para ver realmente qu茅 tan efectivo es el dise帽o de Plug, imaginemos un escenario en el que necesitamos verificar una serie de condiciones y luego redirigir o detenernos si una condici贸n falla. Sin enchufe, terminar铆amos con algo como esto:
defmodule HelloWeb.MessageController do
use HelloWeb, :controller
def show(conn, params) do
case Authenticator.find_user(conn) do
{:ok, user} ->
case find_message(params["id"]) do
nil ->
conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/")
message ->
if Authorizer.can_access?(user, message) do
render(conn, :show, page: message)
else
conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/")
end
end
:error ->
conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/")
end
end
end
¿Observa c贸mo unos pocos pasos de autenticaci贸n y autorizaci贸n requieren anidamiento y duplicaci贸n complicados? Mejoremos esto con un par de enchufes.
defmodule HelloWeb.MessageController do
use HelloWeb, :controller
plug :authenticate
plug :fetch_message
plug :authorize_message
def show(conn, params) do
render(conn, :show, page: conn.assigns[:message])
end
defp authenticate(conn, _) do
case Authenticator.find_user(conn) do
{:ok, user} ->
assign(conn, :user, user)
:error ->
conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") |> halt()
end
end
defp fetch_message(conn, _) do
case find_message(conn.params["id"]) do
nil ->
conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") |> halt()
message ->
assign(conn, :message, message)
end
end
defp authorize_message(conn, _) do
if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do
conn
else
conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt()
end
end
end
Para que todo esto funcione, convertimos los bloques de c贸digo anidados y usamos halt(conn) cada vez que llegamos a una ruta de error. La funcionalidad halt(conn) es esencial aqu铆: le dice a Plug que no se debe invocar el siguiente plug.
Al final del d铆a, al reemplazar los bloques de c贸digo anidados con una serie aplanada de transformaciones de complemento, podemos lograr la misma funcionalidad de una manera mucho m谩s componible, clara y reutilizable.