Subscribe to access all episodes. View plans →
Published August 31, 2022
Phoenix 1.6
plug_canonical_host 2.0
Follow along with the episode starter on GitHub
Here we have our album application and while it works great there is a problem. Currently, we’re accessing it from new.teacher:4000
, however if we open our other browser tab we see that it can also be accessed from old.teacher:4000
.
In a production application you may have this issue if you’ve changed domains or more commonly you have an application that is accessible at the naked domain and with the www
subdomain. For our example, we want our canonical host to be new.teacher:4000
and we want anyone who visits old.teacher:4000
we want to be redirected with an HTTP status of 301.
Now there are a few different ways we could handle this, but for our example let’s do this at the application level using the PlugCanonicalHost
package. Let’s get started. We’ll go to Hex and grab the plug_canonical_host
package and then we’ll add it to our Mixfile.
mix.exs
...
defp deps do
...
{:plug_canonical_host, "~> 2.0"},
...
end
...
Once it’s added we can go to the command line and download it with mix deps.get
:
$ mix deps.get
...
New:
plug_canonical_host 2.0.1
* Getting plug_canonical_host (Hex package)
With that we can open our endpoint.ex
and we’ll add a plug named plug :canonical_host
to the top of the module so we can handle the redirect before any other plugs are called here. Then let’s define a new private function plug named canonical_host
if you’re new to plugs check out episodes 19 - function plugs and 20 - module plugs.
We’ll need it to accept the conn
and opts
, which we can ignore since we wont need them for this example. This function will be pretty simple. In it we’ll get the canonical host and then call PlugCanonicalHost
to handle the redirect.
It’s recommended to define your canonical host with an environment variable, so let’s open up our dev.exs
and add a config for our host. For a production application we would load the canonical host from the environment variable, but for simplicity let’s add our host here, new.teacher
.
config/dev.exs
...
config :teacher,
canonical_host: System.get_env("CANONICAL_HOST")
...
With that added let’s go back to the endpoint.ex
module and grab our canonical_host
. Then if it exists we’ll call PlugCanonicalHost.init
including the canonical_host
to get the options which we can then use to call PlugCanonicalHost.call
passing in the connection and the options.
And if we don’t have a canonical_host
we’ll let the request proceed and return the conn
.
lib/teacher_web/endpoint.ex
...
plug :canonical_host
defp canonical_host(conn, _opts) do
canonical_host = Application.get_env(:teacher, :canonical_host)
if canonical_host do
opts = PlugCanonicalHost.init(canonical_host: canonical_host)
PlugCanonicalHost.call(conn, opts)
else
conn
end
end
...
Let’s test our changes. We’ll go to the command line and start the server.
$ mix phx.server
...
Alright, from old.teacher:4000
let’s refresh the page. When we do we are redirected to new.teacher:4000
. This looks great, but let’s do one more test.
Going back to the command line we’ll run curl
with the “silent” and “head” option requesting old.teacher
and great - when we do we can see the HTTP status of 301 was returned.
$ curl -sI "http://old.teacher:4000"
HTTP/1.1 301 Moved Permanently
cache-control: max-age=0, private, must-revalidate
content-length: 241
date:
location: http://new.teacher:4000/
server: Cowboy
Our redirect is working and it’s being applied to all pages in our application, but what if we have certain pages we don’t want to redirect. How could we exclude them? For our example, let’s say that we want “/blog” and all the blog posts to be excluded from the canonical host redirect.
To do that we’ll go back to our endpoint.ex
module. And we’ll add two more canonical_host
functions. The first one to pattern match on the individual blog post returning the conn
to allow the request to proceed. Then another to pattern match on the blog index page.
lib/teacher_web/endpoint.ex
...
defp canonical_host(%Plug.Conn{request_path: "/blog/" <> _id} = conn, _opts) do
conn
end
defp canonical_host(%Plug.Conn{request_path: "/blog"} = conn, _opts) do
conn
end
...
With those changes let’s open “/blog” in our browser and we can see it’s old.teacher:4000/blog
. Let’s try to refresh the page and see if it redirects.
Great - when we do it’s not redirect. Our blog is being excluded. We can confirm by going to the command line and running the curl
command for our blog routes…and when we do we can see that neither is being redirect. PlugCanonicalHost
is a great if you need a flexible library that allows you to quickly set up redirects based on your hostname.
Iván Hernández Cazorla
1 year agoWonderful! Thank you for the screencast, really nice package.