Phoenix 是一個 Productive. Reliable. Fast. 的 web framework,以 elixir 實作,運作在 erlang VM 上。
installation
erlang vm
elixir
hex: elixir's package manager
mix local.hexMySQL/PostgreSQL
Node.js for Assets
必須要 v5.3.0 以上
node --versionPhoenix
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ezmix 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.serverSimple 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, :indexhttp://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.callEndpoint 也是一個 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
沒有留言:
張貼留言