11
votes

I am trying to figure out how to configure Plug.Static without any other framework (Phoenix, Sugar, etc); just Cowboy, Plug and Elixir. I just don't know how to put things together in the Router.

  plug :match
  plug Plug.Static, at: "/pub", from: :cerber
  plug :dispatch

  get "/" do
    Logger.info "GET /"
    send_resp(conn, 200, "Hello world\n")
  end
  1. Is the declaration of Plug.Static at the right place ? Shouldn't it be after plug :dispatch ?
  2. Do I need to define an additional route
  3. With this declaration:
    1. what is the URL to reach, say index.html?
    2. where on the file system index.html should be located

I'm just lost.

3

3 Answers

10
votes

Take a look at Plug.Router docs for how :match and :dispatch works. :match will try to find a matching route and :dispatch is going to invoke it. This means Plug.Static in your setup will only be invoked if you have a matching route in your router, which doesn't make sense. You want plug Plug.Static before everything. Remember plugs are just functions that are invoked in the order they are declared.

Other than that, your Plug.Static setup seems ok. Your current configuration will serve assets at "/pub", meaning "/pub/index.html" will look for "priv/static/index.html" in your app. More info here: http://hexdocs.pm/plug/Plug.Static.html

4
votes

This is the answer I was looking for.

In the application start method use the Plug.Router with Cowboy:

defmodule HttpServer.Application do
  require Logger
  use Application

  def start(_type, _args) do
    children = [
      {Plug.Adapters.Cowboy2, scheme: :http, plug: HttpServer.Router, options: [port: 4002]}
    ]

    opts = [strategy: :one_for_one, name: HttpServer.Supervisor]

    Supervisor.start_link(children, opts)
  end
end

The router module looks like this:

defmodule HttpServer.Router do
  use Plug.Router

  plug(Plug.Logger)
  plug(:redirect_index)
  plug(:match)
  plug(:dispatch)

  forward("/static", to: HttpServer.StaticResources)

  get "/sse" do
    # some other stuff...
    conn
  end

  match _ do
    send_resp(conn, 404, "not found")
  end

  def redirect_index(%Plug.Conn{path_info: path} = conn, _opts) do
    case path do
      [] ->
        %{conn | path_info: ["static", "index.html"]}

      ["favicon.ico"] ->
        %{conn | path_info: ["static", "favicon.ico"]}

      _ ->
        conn
    end
  end
end

Here requests to "/static" are forwarded to the HttpServer.StaticResources module, but first, the request path is modified for "/" and "/favicon.ico" with plug(:redirect_index). All static files (*.html, *.ico, *.css, *.js etc.) are placed in the default location (project_dir/priv/static).

Finally, the StaticResource module:

defmodule HttpServer.StaticResources do
  use Plug.Builder

  plug(
    Plug.Static,
    at: "/",
    from: :http_server
  )

  plug(:not_found)

  def not_found(conn, _) do
    send_resp(conn, 404, "static resource not found")
  end
end
3
votes

Everything that José Valim said. And here's a most simple example to add to it:

defmodule Server do
  use Plug.Builder
  plug Plug.Logger
  plug Plug.Static, at: "/", from: "/path/to/static"
end

This will serve all static files in "/path/to/static" at the "/" endpoint.

Have a look at the docs for more options and a deeper explanation.