当前位置: 首页 > news >正文

【Phoenix】插件(Plug)

插件是Phoenix的HTTP层的核心。我们在请求生命周期的每一步中都在和插件打交道,而且,Phoenix的核心组件,如endpoint,路由器和控制器本质上都是插件。让我们一起来看看是什么让Plug如此特别。

插件决定着网络应用中模块的组合。同时也是一个不同网络服务的连接适配抽象层。插件的基本思想是统一“连接”这一概念。这与其他HTTP中间件层(如Rack)不同,在Rack中,请求和响应在中间件栈中是分开的。

Rack 是对 Ruby 的 Net::HTTP 库的封装为一个 Ruby 包,这个包能够让开发者方便易用 Net::HTTP。

插件有两种风格:函数插件和模块插件。

函数插件

让一个函数成为插件需要满足以下两点:

  1. 接受两个参数,第一个是连接结构体(%Plug.Conn{}),第二个是连接选项;
  2. 返回一个连接结构体。

下面是一个例子。

def introspect(conn, _opts) do IO.puts """ Verb: #{inspect(conn.method)} Host: #{inspect(conn.host)}Headers: #{inspect(conn.req_headers)} """ conn
end

该函数做了以下几件事:

  1. 接收一个连接和选项(没有用到)
  2. 向终端输出一些连接信息
  3. 返回一个连接

很简单吧,我们将它添加到 lib/hello_web/endpoint.ex 中看看效果。我们可以将它插入任何地方,因此让我们在将请求分发到路由器之前插入 plug :introspect

defmodule HelloWeb.Endpoint do ...plug :introspect plug HelloWeb.Routerdef introspect(conn, _opts) do IO.puts """ Verb: #{inspect(conn.method)} Host: #{inspect(conn.host)}Headers: #{inspect(conn.req_headers)} """ connend 
end

通过将函数名作为原子传递来插入函数插件,回到浏览器访问 http://localhost:4000 ,你应该能在终端看到类似下面的输出:

Verb: "GET"
Host: "localhost" 
Headers: [...]

我们的插件只是打印来自连接的信息。虽然我们的第一个插件非常简单,但是你可以在里面做任何你想做的事情。

模块插件

模块做为插件只需要实现两个函数:

  • init/1 初始化传递给 call/2 的参数和选项
  • call/2 执行连接转换,其实就是前面的函数插件

我们来写一个插件,将 :locale 放入连接,供下游插件如控制器和视图使用。新建一个文件 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)enddef call(conn, default) do assign(conn, :locale, default)end 
end

将模块插件添加到路由器,在 lib/hello_web/router.ex 中的 :browser 管道中添加 plug HelloWeb.Plugs.Locale, "en"

defmodule HelloWeb.Router do use HelloWeb, :routerpipeline :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 ...

init/1 回调中,我们传递了一个默认区域。同时使用模式匹配定义了 call/2 函数来验证参数中的区域,如果没有,使用默认的“en”。 assign/3Plug.Conn 模块的一部分,我们用它来向 conn 存储值。

lib/hello_web/controllers/page_html/home.html.heex 模板的 </h1> 标签之后添加下面的代码:

<p>Locale: <%= @locale %></p>

访问 http://localhost:4000/ 应该能看到显示出了区域,访问 http://localhost:4000/?locale=fr 可以看到区域变成了”fr”。你可以使用这个信息结合Gettext提供国际化的网络应用。

以上就是插件的全部内容,Phoenix从上到下都采用了这种可拔插设计,来看几个例子。

插入位置

endpoint,路由器和控制器都接受插件。

Endpoint插件

Endpoint管理着应用到所有请求的公共插件,并在请求分发到路由器之前应用。通过以下方式向endpoint添加插件:

defmodule HelloWeb.Endpoint do ...plug :introspect plug HelloWeb.Router

Endpoint中的默认插件做了很多工作:

  • Plug.Static - 静态资源服务。因为该插件在日志插件之前,所以静态资源请求没有记录日志。
  • Phoenix.LiveDashboard.RequestLogger - 为Phoenix的动态看板设置请求日志,this will allow you to have the option to either pass a query parameter to stream requests logs or to enable/disable a cookie that streams requests logs from your dashboard.
  • Plug.RequestId - 为每个请求生成一个唯一的请求ID。
  • Plug.Telemetry - 添加检测点让Phoenix可以记录请求路径,状态码和请求时间。
  • Plug.Parsers - 解析请求体,如果有可用的解析器的话。默认情况下,插件可以处理URL编码参数,multiparty和JSON(使用Jason)。如果请求的content-type不能被解析,请求体不会发生变化。
  • Plug.MethodOverride - 根据 _method 参数将POST请求的方法改为PUT,PATCH或DELETE。
  • Plug.Head - 将HEAD请求转换成GET并去掉请求体。
  • Plug.Session - 设置session管理器。注意,使用session之前任然需要先调用 fetch_session/2 ,因为该插件只是设置了session的获取方式。

在endpoint的中间,有一个条件语句块:

if code_reloading? do socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socketplug Phoenix.LiveReloader plug Phoenix.CodeReloader plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello
end

上面的代码只在开发环境运行,它提供了:

  • 热重载 - 如果你修改了CSS文件,浏览器会自动更新而不需要刷新网页;
  • 代码重载 - 修改代码不需要重启服务器;
  • 检查存储库状态 - 保证数据库是最新的,否则抛出错误。

路由器插件

在路由器中,我们可以在pipeline内定义插件:

defmodule HelloWeb.Router do use HelloWeb, :routerpipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.LayoutView, :root}plug :protect_from_forgery plug :put_secure_browser_headers plug HelloWeb.Plugs.Locale, "en"endscope "/", HelloWeb do pipe_through :browserget "/", PageController, :index end

路由定义在scope内,scope可以有多个pipeline。一旦有路由匹配,Phoenix就会调用路由关联的所有pipeline中的所有插件。例如,访问“/”会执行 :browser pipeline,然后调用它的全部插件。

后面我们会看到,pipeline本身也是插件。

控制器插件

最后,控制器也是插件,因此我们可以:

defmodule HelloWeb.PageController do use HelloWeb, :controllerplug HelloWeb.Plugs.Locale, "en"

不同的是,控制器插件允许我们针对某个特定的函数执行插件。例如:

defmodule HelloWeb.PageController do use HelloWeb, :controllerplug HelloWeb.Plugs.Locale, "en" when action in [:index]

这样插件只会针对 index 函数执行。

插件组合

根据插件使用公约,我们将应用请求转化成一些列显示转换。不止于此,为了展示插件设计的真正威力,让我们假设一个场景,我们需要检查一系列条件,在条件失败时重定向或停止。不用插件,代码如下:

defmodule HelloWeb.MessageController do use HelloWeb, :controllerdef 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"/") endend 
end

认证和授权几个简单的步骤竟需要如此复杂的嵌套,让我们通过插件来改进它。

defmodule HelloWeb.MessageController do use HelloWeb, :controllerplug :authenticate plug :fetch_message plug :authorize_messagedef show(conn, params) do render(conn, :show, page: conn.assigns[:message])enddefp 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() endenddefp 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 enddefp authorize_message(conn, _) do if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do connelse conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt() endend 
end

当进入失败分支时,我们使用 halt(conn) 来告诉 plug 不再调用下一个插件。

最终,通过用一系列扁平的插件转换替换了嵌套的代码块,以一种更加灵活、清晰和可重用的方式实现了相同的功能。

http://www.dtcms.com/a/603393.html

相关文章:

  • 做网站用哪里的服务器比较好如何查看网站架构
  • 网站建设方案确认表华为云云速建站
  • 营销型网站如何建设常德经开区网站官网
  • 网站建设皿金手指谷哥壹柒宽屏wordpress主题
  • 高防IP真能100%防御DDoS攻击吗?
  • 网站开发主管要做什么seo网站优化方
  • 电子商务网站搜索引擎设计网站设计前景怎样
  • 山东建设官方网站免费wordpress主题下载地址
  • 六安城市网官网百度小程序排名优化
  • 服务器网站部署建电子商务网站注意事项
  • 有没有做奥数题的网站如何重新运行wordpress
  • 如何建网站挣钱做网站切图软件
  • 什么攻击类型适合使用高防IP进行防护?
  • 网站seo优化方案图片代码如何做网站
  • 南京建网站苏州十大互联网公司
  • 构建AI智能体:九十二、智能协作的艺术:大模型上下文与Token优化指南
  • app软件网站开发网站建设包含那些 内容
  • 个人建网站需要多少钱百度域名值多少钱
  • Python趣味算法:掌握猜牌术算法:用Python解密魔术师的洗牌奥秘
  • wordpress站群 会员网站建设哪家好首选万维科技
  • 怎么做一个盈利网站营销活动管理系统
  • 【Java SE 基础学习打卡】12 Java 入门程序
  • 宁波网站制作相信荣胜网络杭州建模培训
  • 1个月能学好网站开发吗怎么把自己做的网站发布到网上
  • wordpress网站更换空间东莞制作网站
  • 太原市零元网站建设申请网站建设经费
  • 南京大学计算机组织结构【1,2,3讲复习】
  • 编译语言选择 | 探索如何根据项目需求选择合适的编程语言
  • 怎么做校园表白网站东莞网站设计与网站制作
  • 拓者设计官网网页版百度seo关键词点击软件