Phoenix Unplugged; uncovering the magic of plugs

A presentation at ElixirConf 2024 in August 2024 in Orlando, FL, USA by Divya

Slide 1

Slide 1

Phoenix Unplugged; uncovering the magic of plugs

Slide 2

Slide 2

Request Response Cycle request Client (browser) Server (API, DB etc) response

Slide 3

Slide 3

Request Response Cycle request Client (browser) Server (API, DB etc) response a a a tcp p rsing routing get d t response send

Slide 4

Slide 4

Request Client Response Client Server

Slide 5

Slide 5

Request <R nch Process> <Thous nd Isl nd Process> Cowboy B ndit Client <Stre m> Phoenix router Phoenix Router controllers Phoenix Controller etc Phoenix Helpers a a a a a Server Response Client

Slide 6

Slide 6

Request <R nch Process> <Thous nd Isl nd Process> Cowboy B ndit Client <Stre m> Phoenix router Phoenix Router Fr mework (middlew re + plugs) controllers Phoenix Controller etc Phoenix Helpers a a a a a a a Server Response Client

Slide 7

Slide 7

Divya Sasidharan @shortdiv

Slide 8

Slide 8

Request <R nch Process> <Thous nd Isl nd Process> Cowboy B ndit Client <Stre m> Phoenix router Phoenix Router Fr mework (middlew re + plugs) controllers Phoenix Controller etc Phoenix Helpers a a a a a a a Server Response Client

Slide 9

Slide 9

Request <R nch Process> <Thous nd Isl nd Process> Cowboy B ndit Client <Stre m> Phoenix router Phoenix Router Fr mework (middlew re + plugs) controllers Phoenix Controller How does this work? etc Phoenix Helpers a a a a a a a Server Response Client

Slide 10

Slide 10

Wh t is cowboy/b ndit? a a a Cowboy/Bandit is a small, HTTP server for Erlang/OTP, used in the elixir ecosystem.

Slide 11

Slide 11

Wh t is r nch/thous nd isl nd? a a a a a Ranch/Thousand Island is a socket acceptor pool library for Erlang/OTP, commonly used in the Elixir ecosystem to manage TCP connections.

Slide 12

Slide 12

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application defmodule Handler do def init(req, _opts) do resp = :cowboy_req.reply( _status = 200, _body = “<!doctype html><h1>Hello, Cowboy!</h1>”, _request = req ) {:ok, resp, []} end end def start(type, args) do routes = :cowboy_router.compile([ {:, [ {:, Handler, []} ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) application.ex

Slide 13

Slide 13

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application defmodule Handler do def init(req, _opts) do resp = :cowboy_req.reply( _status = 200, _body = “<!doctype html><h1>Hello, Cowboy!</h1>”, _request = req ) {:ok, resp, []} end end def start(type, args) do routes = :cowboy_router.compile([ {:, [ {:, Handler, []} ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) application.ex

Slide 14

Slide 14

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(type, args) do routes = :cowboy_router.compile([ {:, [ {:, HowdyElixirConf.Web.Handler, []} ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 15

Slide 15

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(_type, args) do routes = :cowboy_router.compile([ {:, [ {“/”, HowdyElixirConf.Web.RootHandler, []} ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 16

Slide 16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init(req, _opts) do resp = :cowboy_req.reply( 200, %{“content-type” => “text/html”}, “<!doctype html><h1>Howdy Elixir Conf!</h1>”, req ) {:ok, resp, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 17

Slide 17

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init(req, _opts) do # do some auth {AuthHeader, Req1} = cowboy_req:header(<<”authorization”>>, Req0) resp = if is_authorized(AuthHeader) do :cowboy_req.reply( 200, %{“content-type” => “text/html”}, “<!doctype html><h1>Howdy Elixir Conf!</h1>”, req ) else :cowboy_req.reply( 404, %{“content-type” => “text/html”}, “<!doctype html><h1>Route not found</h1>”, req ) end {:ok, resp, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 18

Slide 18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init(req, _opts) do # do some auth {AuthHeader, Req1} = cowboy_req:header(<<”authorization”>>, Req0) resp = if is_authorized(AuthHeader) do :cowboy_req.reply( 200, %{“content-type” => “text/html”}, “<!doctype html><h1>Howdy Elixir Conf!</h1>”, req ) else :cowboy_req.reply( 404, %{“content-type” => “text/html”}, “<!doctype html><h1>Route not found</h1>”, req ) end {:ok, resp, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 19

Slide 19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init(req, _opts) do Req2 = :cowboy_req.stream_reply( 200, %{“content-type” => “text/html”}, req ) Req3 = :cowboy_req.stream_body( “<!doctype html><html><head><title>Hi</title></head><body><h1>”, Req2 ) Req4 = :cowboy_req.stream_body( “Howdy Elixir Conf!</h1><p>Streaming with Cowboy!</p>”, Req3 ) Req5 = :cowboy_req.stream_body( “</body></html>”, Req4 ) {:ok, Req5, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 20

Slide 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init(req, _opts) do Req2 = :cowboy_req.stream_reply( 200, %{“content-type” => “text/html”}, req ) Req3 = :cowboy_req.stream_body( “<!doctype html><html><head><title>Hi</title></head><body><h1>”, Req2 ) Req4 = :cowboy_req.stream_body( “Howdy Elixir Conf!</h1><p>Streaming with Cowboy!</p>”, Req3 ) Req5 = :cowboy_req.stream_body( “</body></html>”, Req4 ) {:ok, Req5, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 21

Slide 21

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 web/root_handler.ex defmodule HowdyElixirConf.Web.RootHandler do def init( req, _opts) do Req2 = :cowboy_req.stream_reply( 200, %{“content-type” => “text/html”}, req ) Req3 = :cowboy_req.stream_body( “<!doctype html><html><head><title>Hi</title></head><body><h1>”, Req2 ) Req4 = :cowboy_req.stream_body( “Howdy Elixir Conf!</h1><p>Streaming with Cowboy!</p>”, Req3 ) Req5 = :cowboy_req.stream_body( “</body></html>”, Req4 ) {:ok, Req5, []} end def terminate(_reason, _req, _state) do :ok end end

Slide 22

Slide 22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Web.RootHandler do import Plug.Conn web/root_handler.ex def init(conn, _opts) do conn = send_chunked(conn, 200) {:ok, conn} = chunk( conn, “<!doctype html><html><head><title>Hi</title></head><body><h1>” ) {:ok, conn} = chunk( conn, “Howdy Elixir Conf!</h1><p>Streaming with Cowboy!</p>” ) {:ok, conn} = chunk( conn, “</body></html>” ) conn end def terminate(_reason, _req, _state) do :ok end end

Slide 23

Slide 23

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(_type, args) do routes = :cowboy_router.compile([ {:, [ {“/”, HowdyElixirConf.Web.RootHandler, []} ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 24

Slide 24

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(_type, args) do routes = :cowboy_router.compile([ {:, [ {“/”, HowdyElixirConf.Web.RootHandler, []} {“/another”, HowdyElixirConf.Web.AnotherHandler, []} {“/one”, HowdyElixirConf.Web.OneHandler, []} … ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 25

Slide 25

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(_type, args) do routes = :cowboy_router.compile([ {:, [ {“/”, HowdyElixirConf.Web.RootHandler, []} {“/another”, HowdyElixirConf.Web.AnotherHandler, []} {“/one”, HowdyElixirConf.Web.OneHandler, []} … ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 26

Slide 26

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule HowdyElixirConf.Application do use Application application.ex def start(type, args) do routes = :cowboy_router.compile([ {:, [ {:, HowdyElixirConf.Web.PageHandler, HowdyElixirConf.Web.Router } … ]} ]) :cowboy.start_clear( :hello_http, [port: 4001], %{env: %{dispatch: routes}} ) children = [] opts = [strategy: :one_for_one, name: Campsite.Supervisor] Supervisor.start_link(children, opts) end end

Slide 27

Slide 27

1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 15 2 16 3 17 4 18 5 19 6 20 7 21 8 22 9 23 10 24 11 25 12 26 13 27 14 28 15 29 16 30 17 web/router.ex defmodule HowdyElixirConf.Web.Router do use Plug.Router alias HowdyElixirConf.Web.PageController get “/”, PageController, :home get “/two”, PageController, :two get not_matched, PageController, :not_matched, %{path: not_matched} end defmodule HowdyElixirConf.Web.PageHandler do def init(req, router) do path = :cowboy_req.path(req) conn = %Plug.Conn{req_path: path} conn = router.call(conn) web/page_handler.ex resp = :cowboy_req.reply(conn.status, conn.resp_body, req) {:ok, resp, router} end def terminate(_reason, _req, _state) do :ok end end

Slide 28

Slide 28

1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 15 2 16 3 17 4 18 5 19 6 20 7 21 8 22 9 23 10 24 11 25 12 26 13 27 14 28 15 29 16 30 17 web/router.ex defmodule HowdyElixirConf.Web.Router do use Plug.Router alias HowdyElixirConf.Web.PageController get “/”, PageController, :home get “/two”, PageController, :two get not_matched, PageController, :not_matched, %{path: not_matched} end defmodule HowdyElixirConf.Web.PageHandler do def init(req, router) do path = :cowboy_req.path(req) conn = %Plug.Conn{req_path: path} conn = router.call(conn) web/page_handler.ex resp = :cowboy_req.reply(conn.status, conn.resp_body, req) {:ok, resp, router} end def terminate(_reason, _req, _state) do :ok end end

Slide 29

Slide 29

Wh t is plug? a a Plugs are modules in Elixir used to build composable web applications. Each plug can intercept, modify, or respond to an HTTP request or response.

Slide 30

Slide 30

Function Plug Any function that receives a conn and a set of options and returns a conn. It has this type signature: (Plug.Conn.t, Plug.opts) :: Plug.Conn.t Module Plug An extension of the function plug. But must export 2 functions: (Plug.Conn.t(), any()) :: Plug.Conn.t() def call/2 (opts :: any()) :: any() def init/1

Slide 31

Slide 31

Function Plug (Plug.Conn.t, Plug.opts) :: Plug.Conn.t defmodule HowdyElixirConf.Web.Router do use Plug.Router plug :set_custom_header get “/”, HowdyElixirConf.Web.PageController, :home # Define the function plug defp set_custom_header(conn, _opts) do conn |> Plug.Conn.put_resp_header(“x-custom-header”, “HowdyHeader”) end end

Slide 32

Slide 32

Function Plug (Plug.Conn.t, Plug.opts) :: Plug.Conn.t defmodule HowdyElixirConf.Web.Router do use Plug.Router plug :set_custom_header get “/”, HowdyElixirConf.Web.PageController, :home # Define the function plug defp set_custom_header(conn, _opts) do conn |> Plug.Conn.put_resp_header(“x-custom-header”, “HowdyHeader”) end end

Slide 33

Slide 33

Module Plug (Plug.Conn.t(), any()) :: Plug.Conn.t() def call/2 (opts :: any()) :: any() def init/1 defmodule Plug.CustomHeader do import Plug.Conn @spec init(opts :: any()) :: any() def init(opts), do: opts end @spec call(conn :: Plug.Conn.t(), opts :: any()) :: Plug.Conn.t() def call(conn, _opts) do conn |> put_resp_header(“x-custom-header”, “MyAppHeader”) end end

Slide 34

Slide 34

Module Plug (Plug.Conn.t(), any()) :: Plug.Conn.t() def call/2 (opts :: any()) :: any() def init/1 defmodule HowdyElixirConf.Web.Router do use Plug.Router plug Plug.CustomHeader get “/”, HowdyElixirConf.Web.PageController, :home end

Slide 35

Slide 35

Module Plug (Plug.Conn.t(), any()) :: Plug.Conn.t() def call/2 (opts :: any()) :: any() def init/1 defmodule HowdyElixirConf.Web.Router do use Plug.Router plug Plug.CustomHeader get “/”, HowdyElixirConf.Web.PageController, :home end

Slide 36

Slide 36

The mighty Plug.Conn defstruct adapter: {Plug.MissingAdapter, nil}, assigns: %{}, body_params: %Unfetched{aspect: :body_params}, cookies: %Unfetched{aspect: :cookies}, params: %Unfetched{aspect: :params}, path_info: [], query_params: %Unfetched{aspect: :query_params}, query_string: “”, req_cookies: %Unfetched{aspect: :cookies}, req_headers: [], request_path: “”, resp_body: nil, resp_cookies: %{}, scheme: :http, state: :unset, status: nil …

Slide 37

Slide 37

Plug.Router defmodule HowdyElixirConf.Web.Router do use Plug.Router plug :match plug :dispatch get “/hello” do send_resp(conn, 200, “world”) end match _ do send_resp(conn, 404, “oops”) end end

Slide 38

Slide 38

Plug.Router a a spoiler lert A router is plug

Slide 39

Slide 39

Plug.Router defmodule Plug.Router do defmacro using(opts) do quote location: :keep do import Plug.Router use Plug.Builder, unquote(opts) def match(conn, _opts) do … end … end … end end

Slide 40

Slide 40

Plug.Router defmodule Plug.Router do defmacro using(opts) do quote location: :keep do import Plug.Router use Plug.Builder, unquote(opts) def match(conn, _opts) do … end … end … end end

Slide 41

Slide 41

Plug.Router defmodule Plug.Router do defmacro using(opts) do quote location: :keep do import Plug.Router use Plug.Builder, unquote(opts) def match(conn, _opts) do … end defmacro using(opts) do quote do … @behaviour Plug end … def init(opts), do: opts end end end def call(conn, opts) do … end … end end

Slide 42

Slide 42

Let’s m ke router a a a a a bew re the met progr mming

Slide 43

Slide 43

Let’s m ke router a a a a a bew re the met progr mming

Slide 44

Slide 44

a a f But irst: A met progr mming primer

Slide 45

Slide 45

defmodule Greetable do defmacro using(opts) do greeting = Keyword.get(opts, :greeting, “Hello”) llows Greetable to inject functions into other modules. de ine the code th t will be injected quote do def greet(name) do IO.puts(unquote(greeting) <> “, ” <> name <> “!”) end def farewell(name) do IO.puts(“Goodbye, ” <> name <> “!”) end end end end defmodule EnglishGreeter do use Greetable end defmodule SpanishGreeter do use Greetable, greeting: “Hola” end implements the greet nd farewell func p ssed to the m cro s opts a a a EnglishGreeter.greet(“Alice”) # Outputs: Hello, Alice! SpanishGreeter.greet(“Bob”) # Outputs: Hola, Bob! EnglishGreeter.farewell(“Charlie”) # Outputs: Goodbye, Charlie! a f a a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Slide 46

Slide 46

B ck to plugs a a let’s m ke this router go brrrr

Slide 47

Slide 47

The Rules of Plug 1.Must implement init/1 and call/2 a 2.Must use Plug.Conn

Slide 48

Slide 48

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do def init(opts), do: opts def call(conn, _opts) do end end end end spaghetti/router.ex

Slide 49

Slide 49

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug def init(opts), do: opts def call(conn, _opts) do end end end end spaghetti/router.ex

Slide 50

Slide 50

defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug def init(opts), do: opts def call(conn, _opts) do a end end end end a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 spaghetti/router.ex modules th t use this module, import this Implements plug beh viour

Slide 51

Slide 51

defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug spaghetti/router.ex modules th t use this module, import this Implements plug beh viour def init(opts), do: opts def call(conn, _opts) do end end end end defmodule HowdyElixirConf.Web.Router do use Spaghetti.Router # The use macro above expands to include: # import Spaghetti.Router # def call(conn) do … end a end a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 17 2 18 3 19 4 20 5 21 6 22 7 23 8 24 9 25 10 26 11 27 12 28 13 29 14 30 15 web/router.ex

Slide 52

Slide 52

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug def init(opts), do: opts def call(conn, _opts) do end end end end spaghetti/router.ex

Slide 53

Slide 53

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug def init(opts), do: opts def call(conn, _opts) do end end end defmacro get(path, controller, action) do quote do end end end spaghetti/router.ex

Slide 54

Slide 54

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug spaghetti/router.ex def init(opts), do: opts def call(conn, _opts) do content_for(conn.request_path, conn) end end end defmacro get(path, controller, action) do quote do defp content_for(unquote(path), conn) do apply(unquote(controller), :call, [conn, unquote(action)]) end end end end

Slide 55

Slide 55

defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug spaghetti/router.ex def init(opts), do: opts def call(conn, _opts) do content_for(conn.request_path, conn) end end end defmacro get(path, controller, action) do quote do defp content_for(unquote(path), conn) do apply(unquote(controller), :call, [conn, unquote(action)]) end end end end dyn mic lly invokes the c ll/2 function on the controller module, p ssing in the conn nd ction s rguments. a a a a Kernel.apply/3 a a a a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Slide 56

Slide 56

spaghetti/router.ex defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug def init(opts), do: opts def call(conn, _opts) do content_for(conn.request_path, conn) end end end defmacro get(path, controller, action) do quote do defp content_for(unquote(path), conn) do apply(unquote(controller), :call, [conn, unquote(action)]) end end end end dyn mic lly invokes the c ll/2 function on the controller module, p ssing in the conn nd ction s rguments. a a a a a a Kernel.apply/3 a a a a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 (module, function n me, rgs)

Slide 57

Slide 57

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 defmodule Spaghetti.Router do defmacro using(_opts) do quote do import Spaghetti.Router @behaviour Plug spaghetti/router.ex def init(opts), do: opts def call(conn, _opts) do content_for(conn.request_path, conn) end end end defmacro get(path, controller, action) do quote do defp content_for(unquote(path), conn) do apply(unquote(controller), :call, [conn, unquote(action)]) end end end end

Slide 58

Slide 58

14 15 16 17 18 19 20 21 22 23 24 1 25 2 26 3 27 4 28 5 29 6 30 7 31 8 32 9 33 10 1 34 11 2 35 12 3 36 13 4 37 14 5 38 15 6 39 16 7 40 17 8 41 18 9 42 19 10 43 20 11 44 spaghetti/router.ex defmacro get(path, controller, action) do quote do defp content_for(unquote(path), conn) do apply(unquote(controller), :call, [conn, unquote(action)]) end end end end defmodule HowdyElixirConf.Web.Router do use Spaghetti.Router web/router.ex alias HowdyElixirConf.Web.PageController get “/”, PageController, :home end defmodule HowdyElixirConf.Web.PageController do import Plugs.Conn def call(conn, action) do apply(MODULE, action, [conn]) end def home(conn, _) do put_resp_body(conn, “<h1>Howdy Elixir!</h1>”) end … web/page_controller.ex

Slide 59

Slide 59

Pony up! Let’s see some code.