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
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
沒有留言:
張貼留言