MildlySerious
MildlySerious

Reputation: 9190

How to correctly reroute arbitrary paths to files for Plug.Static files?

I was thinking of skipping Phoenix since I am just planning to build a demo for a React app that uses some API routes for its state. Seems like an opportunity to familiarize with the underlying tech.

I came up with the following, but it feels very "hardcoded" and I am curious if there is a more elegant solution to achieve the same thing.

defmodule DemoApp.Plug.ServeStatic do
  use Plug.Builder

  @static_opts [at: "/", from: "priv/static"]

  plug :default_index
  plug Plug.Static, @static_opts
  plug :default_404
  plug Plug.Static, Keyword.put(@static_opts, :only, ["error_404.html"])

  # Rewrite / to "index.html" so Plug.Static finds a match
  def default_index(%{request_path: "/"} = conn, _opts) do
    %{conn | :path_info => ["index.html"]}
  end
  def default_index(conn, _), do: conn

  # Rewrite everything that wasn't found to an existing error file
  def default_404(conn, _opts) do
    %{conn | :path_info => ["error_404.html"]}
  end
end

The idea is to have / serve index.html without redirect, and serve the contents of an error file whenever something isn't found, instead of a minimal response "404 file not found" string.

Is there a way to achieve this without plugging Plug.Static twice, or is this the way to go? I can also see the catchall :default_404 collide with my API routes later on, and I am not sure how I would resolve that.

Any input would be much appreciated. Thank you!

Upvotes: 2

Views: 1140

Answers (1)

Dogbert
Dogbert

Reputation: 222428

I would use Plug.Router and Plug.Conn.send_file/5 for this. Here's some code that does what you do but much cleaner:

defmodule M do
  use Plug.Router

  plug Plug.Static, at: "/", from: "priv/static"
  plug :match
  plug :dispatch

  get "/" do
    send_file(conn, 200, "priv/static/index.html")
  end

  match _ do
    send_file(conn, 404, "priv/static/404.html")
  end
end

As the :match and :dispatch are plugged in after Plug.Static, any files in priv/static will be served before falling back to the router, just like Phoenix does.

With these files in priv/static:

➜ cat priv/static/404.html
404.html
➜ cat priv/static/index.html
index.html
➜ cat priv/static/other.html
other.html

Here's how this code works:

➜ curl http://localhost:4000
index.html
➜ curl http://localhost:4000/
index.html
➜ curl http://localhost:4000/index.html
index.html
➜ curl http://localhost:4000/other.html
other.html
➜ curl http://localhost:4000/foo
404.html
➜ curl http://localhost:4000/foo/bar
404.html
➜ curl http://localhost:4000/404.html
404.html
➜ curl -s -I http://localhost:4000/foo | grep HTTP
HTTP/1.1 404 Not Found
➜ curl -s -I http://localhost:4000/foo/bar | grep HTTP
HTTP/1.1 404 Not Found
➜ curl -s -I http://localhost:4000/404.html | grep HTTP
HTTP/1.1 200 OK

Upvotes: 7

Related Questions