Phoenix Framework from HTTP Request to Response
This post describes the steps that a HTTP request/response takes in a Phoenix Framework application. The example application described here was generated using the command mix phoenix.new hello_world
.
The steps in the HTTP request/response cycle are outlined as follows:
- HTTP Request
- Cowboy
- Plug
- Phoenix Endpoint
- Phoenix Router
- Phoenix Controller
- Phoenix View
- Phoenix Template
- Phoenix Endpoint
- Plug
- HTTP Response
HTTP Request
A browser user sends an HTTP GET request to http://localhost:4000/
.
The request is received by the Cowboy server.
Cowboy
Cowboy is an Erlang based HTTP server which is currently the only server with a Plug adapter implemented.
Cowboy will parse the HTTP request and the first Plug Connection will be created in Plug’s Cowboy adapter.
Plug
Plug is a web server interface for Elixir that provides a way to compose modules in a sequence during a request/response cycle.
Each plug module receives the HTTP request which is called a Plug Connection and often referred to as the variable conn
. The plug may transform and update the connection and then return an HTTP response immediately or pass the connection to the next plug in the sequence.
The Plug library has built in plugs that provide CSRF protection, sessions, logging, serving static files, and more.
Phoenix applications themselves are built by composing a series of plugs which are defined in a Phoenix Endpoint.
Phoenix Endpoint
The Phoenix Endpoint is found in the project file lib/hello_world/endpoint.ex
. The endpoint defines the plugs that will make up your application. It is the entry and exit point to the Phoenix application.
Each plug will be called in order as defined in the HelloWorld.Endpoint
. A plug such as the Plug.Static
plug may send a response before the connection is seen by other plugs. Note that the last plug defined is the router.
defmodule HelloWorld.Endpoint do
use Phoenix.Endpoint, otp_app: :hello_world
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug Plug.Static,
at: "/", from: :hello_world, gzip: false,
only: ~w(css images js favicon.ico robots.txt)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
end
plug Plug.Logger
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
plug Plug.MethodOverride
plug Plug.Head
plug Plug.Session,
store: :cookie,
key: "_hello_world_key",
signing_salt: "0yg9mHDO"
plug :router, HelloWorld.Router
end
Phoenix Router
A Router defines an application’s pipelines and paths. If the given URL path matches based on the HTTP verb and path, the indicated controller action will be run.
Pipelines are defined using the pipeline
macro which describes a sequence of plugs to run on the connection. The scope
macros define which pipelines to run using the pipe_through
macro.
In this example, our GET request to http://localhost:4000/
will match get "/"
definition so the PageController.index
function will be called after the :browser
pipeline plugs are called with the connection. The :browser
pipeline is called because the get "/"
is defined inside a scope that specifies a pipeline using pipe_through
.
The Phoenix Router is analogous to the routes.rb
file in Ruby on Rails. The mix phoenix.routes
command will list the routes and path helpers defined by the router.
defmodule HelloWorld.Router do
use HelloWorld.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWorld do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
# Other scopes may use custom stacks.
# scope "/api", HelloWorld do
# pipe_through :api
# end
end
Phoenix Controller
The controller is where application logic is done. Often this will include running SQL queries using the Ecto
database library which is not covered in this article.
The params variable will contain a string keyed map of request parameters. Next, Phoenix.Controller.render/2
is called with the conn
and template name index.html
. Often, Phoenix.Controller.render/3
will be called with additional data to render like this: render conn, "index.html", [data: "data"]
.
The Phoenix.Controller.render/2
method will lookup the template file which by convention should be located in web/templates/page/index.html.eex
and will then call the Phoenix.View.render_to_iodata/3
function.
defmodule HelloWorld.PageController do
use HelloWorld.Web, :controller
plug :action
def index(conn, _params) do
render conn, "index.html"
end
end
Phoenix View
Aside from rendering the template, the view also can provide helper functions and path helpers which will automatically be available to the template.
defmodule HelloWorld.PageView do
use HelloWorld.Web, :view
end
Phoenix Template
By default, an html formatted EEx template was generated. EEx means Embedded Elixir which allows Elixir code to be included in template files.
The index.html.eex
file that was generated only contains html by default. So, an alternative template example is shown here.
<div>
<p><%= "This is Elixir code" %></p>
</div>
HTTP Response
After the view renders the template, the controller will then call the Plug send_resp
method with the rendered template data to return the HTTP response.
The user receives the rendered template in the browser.