2018年9月3日

Phoenix_1_Installation

Phoenix 是一個 Productive. Reliable. Fast. 的 web framework,以 elixir 實作,運作在 erlang VM 上。

installation

  • erlang vm

  • elixir

  • hex: elixir's package manager

    mix local.hex
  • MySQL/PostgreSQL

  • Node.js for Assets

    必須要 v5.3.0 以上

    node --version
  • Phoenix

    $ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
  • mix phoenix.new

    mix 會增加 phoenix.new 的選項

    $ mix -h
    ...
    mix phoenix.new       # Creates a new Phoenix v1.2.5 application

create hello project,預設 DB 是使用 PostgreSQL,我們改用 MySQL

$ mix phoenix.new hello --database mysql

* creating hello/config/config.exs
* creating hello/config/dev.exs
* creating hello/config/prod.exs
* creating hello/config/prod.secret.exs
* creating hello/config/test.exs
* creating hello/lib/hello.ex
* creating hello/lib/hello/endpoint.ex
* creating hello/test/views/error_view_test.exs
* creating hello/test/support/conn_case.ex
* creating hello/test/support/channel_case.ex
* creating hello/test/test_helper.exs
* creating hello/web/channels/user_socket.ex
* creating hello/web/router.ex
* creating hello/web/views/error_view.ex
* creating hello/web/web.ex
* creating hello/mix.exs
* creating hello/README.md
* creating hello/web/gettext.ex
* creating hello/priv/gettext/errors.pot
* creating hello/priv/gettext/en/LC_MESSAGES/errors.po
* creating hello/web/views/error_helpers.ex
* creating hello/lib/hello/repo.ex
* creating hello/test/support/model_case.ex
* creating hello/priv/repo/seeds.exs
* creating hello/.gitignore
* creating hello/brunch-config.js
* creating hello/package.json
* creating hello/web/static/css/app.css
* creating hello/web/static/css/phoenix.css
* creating hello/web/static/js/app.js
* creating hello/web/static/js/socket.js
* creating hello/web/static/assets/robots.txt
* creating hello/web/static/assets/images/phoenix.png
* creating hello/web/static/assets/favicon.ico
* creating hello/test/controllers/page_controller_test.exs
* creating hello/test/views/layout_view_test.exs
* creating hello/test/views/page_view_test.exs
* creating hello/web/controllers/page_controller.ex
* creating hello/web/templates/layout/app.html.eex
* creating hello/web/templates/page/index.html.eex
* creating hello/web/views/layout_view.ex
* creating hello/web/views/page_view.ex

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running npm install && node node_modules/brunch/bin/brunch build

We are all set! Run your Phoenix application:

    $ cd hello
    $ mix phoenix.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phoenix.server

Before moving on, configure your database in config/dev.exs and run:

    $ mix ecto.create

設定 DB 連線資訊

config/dev.exs

config :hello, Hello.Repo,
  adapter: Ecto.Adapters.MySQL,
  username: "root",
  password: "password",
  database: "hello",
  hostname: "localhost",
  pool_size: 10

執行 ecto.create

$ mix ecto.create

啟動 phoenix

$ mix phoenix.server
[info] Running Hello.Endpoint with Cowboy using http://localhost:4000
16:42:29 - info: compiled 6 files into 2 files, copied 3 in 1.1 sec

或是用 IEx (Interactive Elixir) 啟動

$ iex -S mix phoenix.server

網址在 http://localhost:4000

Simple tutorial

在 /web/router.ex 裡面有一段 scope 的定義

  scope "/", Hello do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

所有以 ./ 開頭的 route 都會符合這個規則,pipe_through :browser macro 負責處理一般 browser 的 requests。

增加 /hello 的網址,指定給另一個 Controller

    get "/hello", HelloController, :world
    get "/", PageController, :index

http://localhost:4000/hello 會出現錯誤訊息

UndefinedFunctionError at GET /hello
function Hello.HelloController.init/1 is undefined (module Hello.HelloController is not available)

新增 /web/controllers/hello/hello_controller.ex

defmodule Hello.HelloController do
  @moduledoc false

  use Hello.Web, :controller
  def world(conn, _params) do
    render conn, "world.html"
  end

end

再瀏覽一次 http://localhost:4000/hello 會出現不同的錯誤訊息

UndefinedFunctionError at GET /hello
function Hello.HelloView.render/2 is undefined (module Hello.HelloView is not available)

新增 /web/views/hello_view.ex

defmodule Hello.HelloView do
  use Hello.Web, :view
end

新增 /web/templates/hello/world.html.eex

<h1>From template: Hello world!</h1>

不需要重新啟動 server,reload 網頁就可以看到結果


修改 web/router.ex,把網址當作變數

get "/hello/:name", HelloController, :world

修改 hello_controller.ex

defmodule Hello.HelloController do
  @moduledoc false

  use Hello.Web, :controller
  # external 參數定義為 "name" => name,但內部卻是用atom,也就是 name: name,這是因為 atom table 不會被 GC。
  def world(conn, %{"name" => name}) do
    render conn, "world.html", name: name
  end

end

修改 world.html.eex

<h1>Hello <%= String.capitalize @name %>!</h1>

瀏覽網址 http://localhost:4000/hello/test


lib 跟 web 資料夾的用途

supervision trees 及 long-running processes 要放在 lib

web-related code 包含 models, views, templates, and controllers 這些放在 web

當 code reloading 功能打開時,web 資料夾裡面的程式在被修改後,會自動 reload,但是 lib 資料夾的程式並不會自動 reload,因此 lib 很適合放 long-running services,例如 Phoenix’s PubSub system, the database connection pool 或是自訂的 supervised processes。

.exs 是 Elixir scripts,不會編譯為 .beam files,因此 mix 設定檔 mix.exs 是用 script 而不是 .ex

Phoenix 支援master configuration 加上其他不同環境的設定檔,所以可看到主設定檔 config.exs 裡面有一段

import_config "#{Mix.env}.exs"

就是說明,這是透過 MIX_ENV 環境變數,判斷 prod/dev/test 三種環境,會嵌入不同環境的設定檔

dev.exs
prod.exs
test.exs

另外 production 環境有一個獨立另外嵌入的設定檔 prod.secret.exs,這是用來儲存 production 環境需要被保護的一些密碼,獨立的檔案可保持設定不會進入 git server。

Endpoint

config.exs 包含了 loggin, endpoint 的設定

Endpoint 是 web server 處理 connection 的介面,設定檔裡面只有一個 Hello.Endpoint

# Configures the endpoint
config :hello, Hello.Endpoint,
  url: [host: "localhost"],
  root: Path.dirname(__DIR__),
  secret_key_base: "g8c9YZ5dYGeA.....XkLz5",
  render_errors: [accepts: ~w(html json)],
  pubsub: [name: Hello.PubSub,
           adapter: Phoenix.PubSub.PG2]

而 /lib/hello/endpoint.ex 中 Hello.Endpoint 裡面包含了這些 chaing of functions 及 plugs

defmodule Hello.Endpoint do
    use Phoenix.Endpoint, otp_app: :hello
    
    plug Plug.Static, ...
    plug Plug.RequestId
    plug Plug.Logger
    plug Plug.Parsers, ...
    plug Plug.MethodOverride
    plug Plug.Head
    plug Plug.Session, ...
    plug Hello.Router
end

這在內部其實是用 pipeline 做的,Endpoints 就是對每一個 request 執行 chain of functions。

connection
    |> Plug.Static.call
    |> Plug.RequestId.call
    |> Plug.Logger.call
    |> Plug.Parsers.call
    |> Plug.MethodOverride.call
    |> Plug.Head.call
    |> Plug.Session.call
    |> Hello.Router.call

Endpoint 也是一個 plug,尤其他 plugs 組合而成。application 是以 series of plugs 組成,由 endpoint開始,以 controller 結束,最後一個 plug 是 controller,也就是 web/router.ex 定義的 controller。

connection
    |> endpoint
    |> plug
    |> plug
    ...
    |> router
    |> HelloController

通常 application 只需要一個 endpoint,但也沒有限制只能有一個,如果要支援 http 80 及 https 443 或是 特別的 8080 for admin,就可以增加 endpoints

Router Flow

web/router.ex 是由兩個部分組成的: pipelines 及 route table

defmodule Hello.Router do
  use Hello.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", Hello do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", Hello do
  #   pipe_through :api
  # end
end

有時會需要針對不同的網址,提供不同的 set of tasks/transformations,例如 :browser 是針對 HTML,有提供取得 session 的方法,並有一個稱為 flash 的 user message system,這裡也提供 security service,例如 request forgery protection。

第二個 pipeline 為 :api,是針對 JSON 的 API,只會處理 JSON requests,如果像要轉換為只處理 XML,就要修改這裡的 plug。

hello application 只使用了 :browser,是用 pipe_through :browser 指定的。

一般的 Phoenix application 是這樣組成的

connection
|> endpoint
|> router
|> pipeline
|> controller
  • endpoint: functions for every request
  • connection: 會經過 named pipeline,針對幾種主要類型的 request提供一般化的 functions
  • controller: 會呼叫 data model 並透過 view template 產生頁面

Controllers, Views, and Templates

web 目錄為

└── web
    ├── channels
    ├── controllers
    │ ├── page_controller.ex
    │ └── hello_controller.ex
    ├── models
    ├── static
    ├── templates
    │ ├── hello
    │ │ └── world.html.eex
    │ ├── layout
    │ │ └── app.html.eex
    │ ├── page
    │ │ └── index.html.eex
    ├── views
    │ ├── error_view.ex
    │ ├── layout_view.ex
    │ ├── page_view.ex
    │ └── hello_view.ex
    ├── router.ex
    └── web.ex

最底層為 router.ex 及 web.ex,web.ex 定義了整個 application structure。

在後面會提到 channel 的部分。

  • Erlang VM 會提供 application scalability
  • endpoint 能過濾 static request,並 parse request 然後呼叫 router
  • browser pipeline 會處理 "Accept" header,取得 session,也能防止 Cross-Site Request Forgery(CSRF) 的攻擊

hello application 相關的檔案

connection                      # Plug.Conn
    |> endpoint                 # lib/hello/endpoint.ex
    |> browser                  # web/router.ex
    |> HelloController.world    # web/controllers/hello_controller.ex
    |> HelloView.render(        # web/views/hello_view.ex
        "world.html")           # web/templates/hello/world.html.eex

References

Programming Phoenix

Using MySQL with the Phoenix Framework