3

I have two modules: lib/endpoints/v1/base.ex and lib/endpoints/v2/base.ex.

lib/endpoints/v1/base.ex

defmodule Http.Endpoints.V1.Base do
  require Logger
  use Plug.Router

  plug(:match)
  plug(:dispatch)
  plug(Plug.Logger)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Poison)

  get "/v1/ping" do
    send_resp(conn, 200, "pong!")
  end
end

lib/endpoints/v2/base.ex

defmodule Http.Endpoints.V2.Base do
  require Logger
  use Plug.Router

  plug(:match)
  plug(:dispatch)
  plug(Plug.Logger)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Poison)

  get "/v2/ping" do
    send_resp(conn, 200, "pong! 2")
  end
end

My endpoint works correctly if I put in my applications.ex the children

Plug.Cowboy.child_spec(scheme: :http, plug: Http.Endpoints.V1.Base, options: [port: Application.get_env(:http, :port)])

But I would like my application starts all endpoints versions.

I tried to create lib/endpoints.ex with require Http.Endpoints.V1.Base and require Http.Endpoints.V2.Base and changed my applications.ex but it did not work.

Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
Flavio Milan
  • 467
  • 3
  • 16

2 Answers2

6

You can forward to other routers from your endpoints file. Here is the documentation for the forward/2 function: https://hexdocs.pm/plug/Plug.Router.html#forward/2

Basically you create 2 routers for v1 & v2:

defmodule MyAppWeb.V2.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/ping" do
    send_resp(conn, 200, "OK")
  end
end

and

defmodule MyAppWeb.V1.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/ping" do
    send_resp(conn, 200, "OK")
  end
end

Then in your endpoint you can add all the common functionality and forward to your versioned routes like so:

defmodule MyAppWeb.Endpoint do
  require Logger
  use Plug.Router

  plug :match
  plug :dispatch
  plug Plug.Logger
  plug Plug.Parsers, parsers: [:json], json_decoder: Poison

  # Forwarding
  forward "/v2", to: MyApp.V2.Router
  forward "/v1", to: MyApp.V1.Router

  # You should put a catch-all here
  match _ do
    send_resp(conn, 404, "Not Found")
  end
end

Then in your application.ex file, mount your endpoint as you did before. However, at this point you should be able to ping both /v1/ping and /v2/ping from the same port.

Cheers!

Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
Mike Quinlan
  • 2,873
  • 17
  • 25
  • 1
    Don't we have to match and dispatch on the sub-routers as well? – Victor Schröder Sep 06 '19 at 11:50
  • I'm not 100% sure that it's required, but I remember having to do that in the past. – Mike Quinlan Sep 06 '19 at 12:27
  • 1
    I just tried and it's definitely needed. The code compiles as `Plug.Router` exposes `init/1` and `call/2`, but if we don't add `:match` and `:dispatch` in the pipeline of the sub-routers, the connection will hang or simply fail (because it won't be passed to the paths configured in the router module). I'm adding them to the answer to save some time from people trying this. – Victor Schröder Sep 07 '19 at 17:56
1

My endpoint works correctly if I put in my applications.ex the children

Plug.Cowboy.child_spec(
   scheme: :http, 
   plug: Http.Endpoints.V1.Base, 
   options: [port: Application.get_env(:http, :port)]
)

The modern way to specify that is:

{Plug.Cowboy, [[ 
      scheme: :http, 
      plug: Http.Endpoints.V1.Base, 
      options: [port: Application.get_env(:http, :port)]
    ]]
}

See: https://hexdocs.pm/elixir/Supervisor.html#module-child_spec-1

Although, to me it seems easier to call child_spec() directly, rather than try to figure how many brackets you need around the argument.

7stud
  • 46,922
  • 14
  • 101
  • 127