Request and Response cycle in Elixir: Phoenix

When I was studying how controllers work in Ruby, I learned about rails middleware and how Journey is used to routes the incoming request. In Phoenix, we have something called conn.
If you have worked on phoenix before you might have seen this conn required in almost all the places in your application. The “conn” comes into life when you make a request to a phoenix app and dies when the response is sent out. The conn object carries the whole request and response cycle in Phoenix.
Project Setup
Before setup, I assume you’ve already install elixir and phoenix on your machine.
Let’s first create a phoenix app:
mix phx.new <project_name>cd <project_name>mix ecto.createmix phx.server
Now you can go to, http://localhost:4000/, and you will get a phoenix welcome page.
Erlang and Elixir
We all know the elixir is built upon erlang but to understand erlang will give you a better understanding of the elixir. Both use BEAM which compiles Erlang and Elixir source code into Bytecode. BEAM is a part of the Erlang Run-Time System(ERTS). You might want to check out this link if you want to in-depth Elixr and BEAM.
Cowboy
Erlang has Cowboy and Ranch which uses to create a server in Phoenix. It is the same as Nginx but Cowboy has one more advantage over the other server. Cowboy works in combination with Ranch (which is a socket worker pool for TCP protocols) and Cowlib(library for message processing). Since Cowboy is designed to build server. The process starts when you initialize your server.
cowboy:start_http(http, 10, [{port, 80}], [{env, [{dispatch, Dispatch}]}])After this everything will be handled by Ranch. The cowboy just implements the protocol, for example, HTTP, etc. These protocols are fed into Ranch which will decide how the server will behave.
When the Nginx web server maps a file on the disk to every HTTP request. Cowboy maps every HTTP request to an erlang module. It has no idea about the disk.
dispatch_config = :cowboy_router.compile([
{ :_,
[
{:_, MyApp.Cowboy.Handler, []},
]}
])
{ :ok, _ } = :cowboy.start_http(:http,
100,
[{:port, 8080}],
[{ :env, [{:dispatch, dispatch_config}]}]
)So MyApp.Cowboy.Handler handles all the incoming HTTP requests.
Phoenix App
Let’s come down to your phoenix app which you’ve just created. When you create a project or type this command:
mix phx.new <project_name>`It will start the otp_app. So it means when you create your project Elixir is creating an OTP app. I will not get into how OTP works because it’s another big topic in Elixir's world.
To explain this you have to go your mix.exs file and you will see something like this
def application do
[
mod: {Banking.Application, []},
extra_applications: [:logger, :runtime_tools]
]
endWhen you run the server using mix phx.server, Elixir starts all the applications listed in the “application” and then it will start your phoenix project. So when I say starting an application, it will call the function name “start” in the module give in ‘mod’.
So when we run the server the start function will get invoked in your application file. So go to your lib/<app-name>/application.ex file you’ll see something like this
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Start the Ecto repository
Banking.Repo,
# Start the endpoint when the application starts
BankingWeb.Endpoint
# Starts a worker by calling: Banking.Worker.start_link(arg)
# {Banking.Worker, arg},
]opts = [strategy: :one_for_one, name: Banking.Supervisor]
Supervisor.start_link(children, opts)
end
This function creates a supervisor process that monitors two-child supervisor processes for Repo and Endpoint. A supervisor process doesn’t do any actual work, rather, it only checks if the child processes are working or dead. Since our start function started two child processes, both of which are supervisors, it also means that these child supervisors have one or more workers or supervisors.
We don’t need to see the Repo supervisor because it’s for managing database connections. Now let’s explore Endpoint supervisor.
defmodule BankingWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :banking
socket "/socket", BankingWeb.UserSocket,
websocket: true,
longpoll: false plug Plug.Static,
at: "/",
from: :banking,
gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt) if code_reloading? do
plug Phoenix.CodeReloader
end plug Plug.RequestId
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Phoenix.json_library() plug Plug.MethodOverride
plug Plug.Head plug Plug.Session,
store: :cookie,
key: "_banking_key",
signing_salt: "kSKZtoWp" plug BankingWeb.Router
end
Banking.Endpoint is a child supervisor of the main application in this case Banking is the parent supervisor. You will not see the required start_link function in Endpoint. The line
use Phoenix.Endpoint, otp_app: :bankingis doing a lot of things for you. To get into detail you might have to learn about metaprogramming. Metaprogramming will define the start_link dynamically with all the worker details.
Behind the scene, a lot of things happening which I was not familiar with in starting but when I read about metaprogramming it got me thinking about the functions like ranch. Ranch supervisor is also which is dynamically defined in your phoenix app. This supervisor starts Cowboy process listening at port 4000 on the localhost and is configured to pass all requests to Plug.Cowboy.Handler.
Plug.Adapters.Cowboy.Handler is the module which handles all the request. So what it does is it takes your phoenix application endpoint as an argument and it kind of creates the pipeline between them.
Plug
Before explaining the conn struct it’s important for you to know about the plug.
Plug is a specification for composable modules in between web applications. It is also an abstraction layer for connection adapters of different web servers. The basic idea of Plug is to unify the concept of a “connection” that we operate on. This differs from other HTTP middleware layers such as Rack, where the request and response are separated in the middleware stack. You can think of a plug as a piece of code that receives a data structure, does some sort of transformation, and returns this same data structure, slightly modified. This data structure that a Plug receives and returns is usually called, and represents everything that there is to know about a request.
As plugs always receive and return a connection, they can be easily composable, forming what is called a Plug pipeline. Actually, that is what usually happens. We receive a request, then each plug transforms this request a little bit and pass the result to the next plug, until we get a response.
That’s it for this blog. That was a brief guide on phoenix and how it handles request and response cycle. I will continue this in my next blog where I will explain the conn struct in more detail.
Thankyou

