defmodule Heimdall.DynamicRoutes do @moduledoc """ Module for dynamically registering routes and routing requests. This is where most of the magic happens. All traffic through the application is routed through this plug. Routes are stored in an ETS table to be looked up when the plug is called. When called `Heimdall.DynamicRoutes` search for a route that matches the request, wrap all plugs in the route into one function and call it with the current request. The final plug in the chain will always be `Heimdall.Plug.ForwardRequest` """ import Plug.Conn import Heimdall.Util.PlugUtils alias Heimdall.Plug.ForwardRequest @doc """ Registers a route for later lookup """ def register(tab, host, path, plugs, opts) do true = :ets.insert(tab, {host, path, plugs, opts}) end @doc """ Unregisters a route given its host and path """ def unregister(tab, host, path) do true = :ets.match_delete(tab, {host, path, :_, :_}) end @doc """ Unregisters all routes for a give table """ def unregister_all(tab) do :ets.delete_all_objects(tab) end def init([tab: tab]), do: tab @doc """ Returns the route in given routes that matches a path as a list (which is how plug conns reperesent them internally). ## Examples iex> Heimdall.DynamicRoutes.lookup_path([{"localhost", ["test", "path"], [], {}}], ["test", "path"]) {"localhost", ["test", "path"], [], {}} iex> Heimdall.DynamicRoutes.lookup_path([{"localhost", ["test", "path"], [], {}}], ["test", "path", "but", "longer"]) {"localhost", ["test", "path"], [], {}} """ def lookup_path(routes, conn_path) do Enum.find routes, {nil, nil, [], []}, fn({_, route_path, _, _}) -> split_path = Enum.take(conn_path, length(route_path)) route_path == split_path end end def call(conn, tab) do case :ets.match_object(tab, {conn.host, :_, :_, :_}) do [] -> send_resp(conn, 404, "no routes found") routes -> {_, path, plugs, opts} = lookup_path(routes, conn.path_info) {base, new_path} = Enum.split(conn.path_info, length(path)) new_conn = %{ conn | path_info: new_path, script_name: conn.script_name ++ base } wrap_plugs(plugs, ForwardRequest).(new_conn, opts) end end end