Phoenix Framework: Request-Response Life Cycle
In the previous parts of the Phoenix series, I wrote about Phoenix framework models and migrations, in particular about how they compare to models and migrations in Rails and also about the first steps in Phoenix.
In today’s blog post, I would like to tell you a little bit about the whole lifecycle of a request: from the moment it’s sent by a web browser to receiving a response from the same client. The whole lifecycle can be described in nine steps:
1. HTTP Request Sent by Web Browser
This is pretty simple and standard. A user enters an URL, e.g. https://my-awesome-site.com/articles/wild-fox-jumped-over-brown-dog. The browser creates a request and sends it over the HTTP/HTTPS protocol.
2. Cowboy: Erlang-based HTTP Server
Cowboy receives the HTTP request and creates a connection struct that is usually named conn, using the Plug Cowboy adapter. It will be transformed in the next functions to generate a response. Cowboy uses another library called ranch for the connection pool. Each connection is a separate process on an Erlang virtual machine, so each request is completely independent.
This is a web server interface for Elixir, it’s similar to abstractions such as rack in the Ruby world. Plug gives us a few ready-made implementations: CSRF protection, sessions, logging or serving static files. In a lot of web applications, we would most likely write a plug to check if the user is authenticated and – based on that – show the user a resource or redirect to the login page.
4. Phoenix Endpoint
This is pretty much the definition of what plugs your application is build of. It is the door to Phoenix OTP application. Phoenix can be only a part of your whole application – it doesn’t really have to be the centre of it. Let’s see plugs that are defined here when we create a new app:
The first plug over here is just for serving static files – it adds an HTTP cache header so that the client knows that it doesn’t need to fetch them again. The following two plugs are used for hot reloading in development. Another two are used for adding a request id to the request (catching requests in logs in other platforms, such as elixir, without it could be quite tricky) and logging stuff using a logger implementation defined in the project’s configuration. Plug.Parsers is used to parse the body of a request – here we get params from the request. Plug.MethodOverride is used to override the HTTP method and Plug.Head is used to change the HEAD HTTP method to GET. The last two plugs are used for storing the session and router used by the application.
5. Phoenix Router
Here we just match the route requested by the client to the specific controller and action. We can additionally define specific plugs and pipelines (which are sets of plugs) through which requests should pass. Let’s look at the default router:
We have two pipelines defined here: the first one is for the browser, while the other is for the API calls. We could also add another pipeline, such as :authenticated, where we could check if the user is logged in and fetch the current user for use in a controller or view.
6. Phoenix Controller
This is the part where we handle the individual action. We fetch data from the database, call upon services to execute business logic, render a specific template or render JSON.
7. Phoenix View
Here we can put helper functions used by the template. You can think of it as a presenter pattern.
8. Phoenix Template
Here we generate the HTML that should be added as body to the response. This is actually the view from Ruby on Rails framework.
9. HTTP Response
When view renders the template, it calls the send_resp function to return an HTTP response back to the client. You can check the source code for invoking the render of the template here. send_resp source can be found here.
From this point on, the user can see our awesome web app! In my opinion, this is a lot less complex than the lifecycle of a request in Rails.