• 【Phoenix】请求的生命周期


    本文的目的是讨论Phoenix请求的生命周期。我们实战添加两个新的页面,并讨论整个过程是如何串起来的。

    让我们从添加第一个新页面开始。

    添加一个新页面

    web应用通常通过将HTTP方法和路径映射到应用的某个函数来处理请求。Phoenix通过路由器来实现这个匹配。例如将”/articles”映射到显示文章的函数。因此,添加一个页面首先要添加一个新的路由。

    新建路由

    控制器和动作通过路由器关联它要处理的HTTP方法和路径。在Phoenix中,控制器对应者Elixir的模块,动作是控制器下定义的方法。

    动作本质上就是一个处理请求的函数,在Go语言中,称为处理器函数,Phoenix使用了”action”一词来表述它,翻译为动作确实略显生硬,阅读时可以理解为每个请求对应的动作。但对于其本质一定要拿捏准确。

    对于新的应用,Phoenix为我们生成了一个路由器文件 lib/hello_web/router.ex ,它也是本章的主角。

    在前面例子中欢迎页的路由如下:

    get "/", PageController, :home
    
    • 1

    让我们看看这个路由干了什么。访问 http://localhost:4000 向跟目录发起一个HTTP GET请求。这个请求会被 lib/hello_web/controllers/page_controller.ex 文件定义的 HelloWeb.PageController 中的 home/2 函数处理。

    我们会新建一个页面,当访问 http://localhost:4000/hello 时,输出”Hello World, from Phoenix!”。

    我们要做的第一件事是添加一个页面路由。打开 lib/hello_web/router.ex ,对于一个全新的应用,内容如下:

    defmodule HelloWeb.Router do 
    	use HelloWeb, :router
    	pipeline :browser do 
    		plug :accepts, ["html"] 
    		plug :fetch_session 
    		plug :fetch_live_flash 
    		plug :put_root_layout, html: {HelloWeb.Layouts, :root} 
    		plug :protect_from_forgery 
    		plug :put_secure_browser_headers
    	end
    	pipeline :api do 
    		plug :accepts, ["json"]
    	end
    	scope "/", HelloWeb do 
    		pipe_through :browser
    
    		get "/", PageController, :home 
    	end
    	# Other scopes may use custom stacks. 
    	# scope "/api", HelloWeb do 
    	# pipe_through :api 
    	# end
    	# ... 
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    暂时忽略 pipelinescope ,在稍后的教程中再讨论它们。

    让我们在 scope "/" do 下添加一个路由,将 GET /hello 请求映射到 HelloWeb.HelloControllerindex 方法。

    scope "/", HelloWeb do 
    	pipe_through :browser
    
    	get "/", PageController, :home 
    	get "/hello", HelloController, :index
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    新建controller

    控制器是Elixir模块,动作是模块下的Elixir函数。动作的目的是收集数据并执行渲染。我们需要一个 HelloWeb.HelloController 模块以及一个 index/2 函数。那么动手创建一个 lib/hello_web/controllers/hello_controller.ex 文件,并输入下面的代码:

    defmodule HelloWeb.HelloController do 
    	use HelloWeb, :controller
    	
    	def index(conn, _params) do 
    		render(conn, :index)
    	end 
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    use HelloWeb:controller 再后面的教程中再详细讨论,先将注意力集中到 index 函数。

    一个控制器动作有两个参数,第一个是 conn ,它是一个存储着大量请求数据的结构体;第二个是 params ,它是请求参数。这里为了避免编译器警告,我们在 params 前面加了一个下划线 _

    函数的核心是 render(conn, :index) ,它告诉Phoenix要渲染 index 模板。表示渲染的模块叫做视图,Phoenix视图默认控制器和视图格式来命名,这里控制器是 HelloController ,视图格式是 HTML ,因此我们需要一个 HelloWeb.HelloHTML 模块并定义个 index/1 函数。

    新建视图

    Phoenix视图充当的是展示层。例如,我们希望 index 输出的是完整的HTML页面。为了快乐搬砖,我们常常会用模板创建HTML页面。

    让我们来创建一个视图,新建 lib/hello_web/controllers/hello_html.ex 文件,输入以下代码:

    defmodule HelloWeb.HelloHTML do 
    	use HelloWeb, :html
    
    end
    
    • 1
    • 2
    • 3
    • 4

    我们可以通过函数或者单独的文件向视图添加模板。

    通过函数添加代码如下:

    defmodule HelloWeb.HelloHTML do 
    	use HelloWeb, :html
    
    	def index(assigns) do 
    		~H"""
    		Hello! 
    		"""
    	end 
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们定义了一个接受 assigns 的函数,并使用 ~H 标记添加想要渲染的内容。在 ~H 标记内,我们使用的模板语言叫HEEx,表示”HTML+EEx”。EEx是一个用来嵌入Elixir的库,HTML+EEx是EEx针对HTML的扩展,支持HTML验证,组件,和值的自动转义。后者可使你免受跨站点脚本之类的安全漏洞的影响,而无需额外的工作。

    模板文件原理类似。函数方式适用于短小的模板,模板文件适用于有很多标签或当你感觉函数已难以维护时。

    让我们试着定义一个模板文件。首先删除 def index(assigns) 函数定义,替换成 embed_templates 声明:

    defmodule HelloWeb.HelloHTML do 
    	use HelloWeb, :html
    
    	embed_templates "hello_html/*" 
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里我们告诉 Phoenix.Component 将同级目录 hello_html 下的所有 .heex 模板做为函数定义嵌入我们的模块。

    接下来我们需要向 lib/hello_web/controllers/hello_html 目录添加文件。

    注意看控制器名称 HelloController ,视图名称 HelloHTML 和模板目录 hello_html 都遵循着相同的命名约定,并且它们在目录树中也在一起。

    注意:我们可以任意重命名 hello_html 目录并将它放在 lib/hello_web/controllers 子目录下,但是需要更新 embed_templates 设置。因此建议保持统一的命名约定以避免歧义。

    lib/hello_web

    ├── controllers

    │·····├── hello_controller.ex

    │·····├── hello_html.ex

    │·····├── hello_html

    |·············├── index.html.heex

    模板文件名格式为 NAME.FORMAT.TEMPLATING_LANGUAGE ,我们在 lib/hello_web/controllers/hello_html/ 目录下创建一个名为 index.html.heex 的文件:

    <section> 
    	<h2>Hello World, from Phoenix!h2>
    section>
    
    • 1
    • 2
    • 3

    模板文件会自行编译为模块下的函数,两种方式没有运行时的性能差异。

    现在我们有了路由,控制器,视图和模板,我们可以访问 http://localhost:4000/hello 来看看效果了。

    在这里插入图片描述

    这里有些有趣的事情值得我们注意。当我们做这些变更时,不需要停止和重启服务器。没错,Phoenix具有代码热加载!还有即使我们的 index.html.heex 文件只包含一个简单的 section 标签,我们也得到了一个完整的HTML文档。事实上我们的模板是渲染在一个布局中的:首先渲染的是 lib/hello_web/components/layouts/root.html.heex ,然后它会渲染 lib/hello_web/components/layouts/app.html.heex ,最后是我们的内容。如果你打开这些文件看一看,就会在底部发现这样一行代码:

    <%= @inner_content %>
    
    • 1

    它会在HTML被发送到浏览器之前将模板注入到布局中。关于布局我们会在后面的教程中介绍。

    从endpoint到视图

    我们已经创建了第一个页面,现在可以看看一个请求的生命周期是如何串联起来的了。

    所有的HTTP请求都始于应用的endpoint,其实就是 lib/hello_web/endpoint.ex 文件中的 HelloWeb.Endpoint 模块。当我们打开这个文件查看时,就会发现它里面大量调用了 plug ,跟路由挺像的。Plug是一个库,也是组织web应用的说明书。它是Phoenix处理请求的重要部分,有关细节后面的教程中会讲到。

    目前,可以说每个plug都定义了一个处理请求的队列。在endpoint中,你会看到大致如下的框架:

    defmodule HelloWeb.Endpoint do 
    	use Phoenix.Endpoint, otp_app: :hello
    
    	plug Plug.Static, ... 
    	plug Plug.RequestId 
    	plug Plug.Telemetry, ... 
    	plug Plug.Parsers, ... 
    	plug Plug.MethodOverride 
    	plug Plug.Head 
    	plug Plug.Session, ... 
    	plug HelloWeb.Router
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    每个插件都有不同的作用,后面我们会讲到。最后一个插件恰好就是 HelloWeb.Router 模块。它让endpoint将所有请求的后续处理都交给路由器。路由器的主要作用就是将请求映射到处理器。最后处理器告诉视图渲染一个模板。

    此时,你可能会想,简单地渲染一个页面怎么需要这么多步骤。但是,当应用变得越来越复杂时,我们会看到每一层都有其特殊的作用:

    • endpoint(Phoenix.Endpoint) - endpoint包含所有请求的公共和初始路径,用来处理所有请求都要做的事情。
    • 路由器(Phoenix.Router) - 路由负责将请求分发到控制器,同时也运行我们确定一些功能的范围。比如有些页面需要用户鉴权,有些页面则不需要。
    • 控制器(Phoenix.Controller) - 控制器的工作是提取请求信息,调用业务领域,并为表示层准备数据。
    • 视图 - 视图处理来自控制器的结构化数据,并将其转换为显示给用户的形式。视图通常以它们呈现的内容格式命名。

    让我们再添加一个页面,巩固一下最后三个组件是如何协同工作的。

    这里我保留了endpoint这个单词,本意为端点、终点,直译不好理解,这里endpoint指的其实就是服务端,或者说是服务所有请求的入口点。

    创建新页面

    让我们稍微加一点难度,添加一个页面,它会截取URL的一部分并通过控制器传入模板,最后在页面上显示出来。

    如前面说的,我们首先要做的是创建一个新的路由。

    创建新路由

    这里我们复用之前创建的 HelloController ,添加一个新的 show 方法。在之前的路由下添加一行:

    scope "/", HelloWeb do 
    	pipe_through :browser
    
    	get "/", PageController, :home
    	get "/hello", HelloController, :index 
    	get "/hello/:messenger", HelloController, :show
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意我们在路径中用到了 :messenger 语法,Phoenix会将URL中对应位置的值转成一个变量。例如,我们在浏览器输入 http://localhost:4000/hello/Frank ,messenger的值就是Frank。

    新建动作

    新路由下的请求会由 HelloWeb.HelloControllershow 函数处理。我们已经有了一个控制器 lib/hello_web/controllers/hello_controller.ex ,因此我们唯一需要做的就是在控制器下添加一个 show 函数。这一次,我们需要从参数中提取messenger变量,并传递给模板。为此,将下面的函数添加到控制器:

    def show(conn, %{"messenger" => messenger}) do 
    	render(conn, :show, messenger: messenger)
    end
    
    • 1
    • 2
    • 3

    我们给 render 函数传递了第三个参数,一个键值对。其中 :messenger 是键,变量 messenger 是值。

    如果我们需要访问除messenger之外的请求参数,可以像下面这样定义 show/2 函数

    def show(conn, %{"messenger" => messenger} = params) do 
    	...
    end
    
    • 1
    • 2
    • 3

    要记住, params 的键是字符串,等号不是赋值,而是模式匹配。

    新建模板

    最后我们需要创建一个新的模板,遵循命名规范,将它放在 lib/hello_web/controllers/hello_html 目录下,命名为 show.html.heex 。唯一与 index.html.heex 不同的是,这次我们需要显示messenger变量。

    为此,我们使用特殊的HEEx标签 <%= %> 来求值Elixir表达式。任何出现在标签内的Elixir代码都会被执行,其结果会替换该标签。如果标签内没有等号,代码依然会被执行,但结果不会出现在页面中。

    记住我们的模板是用HEEx(HTML+EEx)编写的,HEEx是EEx的超集,因此也继承了 <%= %> 语法。

    模板内容如下:

    Hello World, from <%= @messenger %>!

    • 1
    • 2
    • 3

    我们从控制器传入视图的值统称为”assigns”,我们可以通过 assigns.messenger 来访问messenger,但是通过元编程,Phoenix为我们提供了更加干净的 @ 语法。

    完成。如果我们用浏览器访问 http://localhost:4000/hello/Frank ,应该会看到下面的页面:

    在这里插入图片描述

  • 相关阅读:
    Scrapy:Python中高效的网络爬虫框架
    vscode的快捷键
    gin架构下实现页面的数据调用
    【mac端mysql】用户权限问题
    mp4转gif在线转换,视频转换成gif动图怎么做?
    http,https,ip,tcp,udp
    预训练word2vec--Word2Vec实现(二)
    STAAD.Pro CONNECT Edition
    高校新生如何选择最优手机流量卡?
    什么是阿里云ecs.c7.large服务器?附性能评测!
  • 原文地址:https://blog.csdn.net/puss0/article/details/134432458