Phoenix: Support subdomains in the Router Helpers

Created on 26 Aug 2017  Â·  13Comments  Â·  Source: phoenixframework/phoenix

Environment

  • Elixir version (elixir -v): Elixir 1.5.1
  • Phoenix version (mix deps): phoenix 1.2.3

    Expected behavior

Url generated on the subdomain should point to the subdomain

Actual behavior

Url generated on the subdomain points to the host specified in the endpoint configuration

This further development of a host-based routing discussed in https://github.com/phoenixframework/phoenix/issues/489:
while router itself supports subdomains, generating correct urls still requires manual work - user has to:

  • deduct current host via conn.host
  • construct %URI{} structure with that host
  • pass URI to the _url helper
uri = %URI{host: conn.host}
MyAppWeb.Router.Helpers.page_url(uri, :show, "hello")
"https://other.example.com/pages/hello"

On the surface adding this piece of functionality is as simple as:

  def url(_router, %Conn{private: private, host: host}) do

    private.phoenix_endpoint.url
    |> URI.parse()
    |> Map.put(:host, host)
    |> URI.to_string()
  end

But it feels more like plumbing than a proper solution and I want to ask whether team has any strategy/vision/plans for the subdomains support in the Phoenix?

Thanks.

enhancement

Most helpful comment

I don't know how it will work with dynamical subdomains, but in case when I deals with something like this:

scope "/", MyApp, host: "subdomain." do
    pipe_through :browser

    get "/some-path", PageController, :index
end

I expect that page_url(conn, :index) will return http://subdomain.example.com/some-path, but instead of this I have http://example.com/some-path and this means that the function returns incorrect URL.

So, it would be great if we'll have something like: page_url(conn, :index, host: "subdomain.") it should works also in case when a subdomain is dynamical.

All 13 comments

Yeah, that would be nice. I am building an app with dynamic subdomains and being able to easily link to a specific subdomain would be awesome.

We could allow you to store something in the connection to be used as the uri prefix for all routes generated. This way you do it once and you don't need to remember about doing it per helper. But then you will need to remember exactly which paths should be whitelisted - going on the opposite route.

Maybe this can be set on the router as a setting, so the router helpers then behave like that? I serve my subdomains via an own router.

I don't know how it will work with dynamical subdomains, but in case when I deals with something like this:

scope "/", MyApp, host: "subdomain." do
    pipe_through :browser

    get "/some-path", PageController, :index
end

I expect that page_url(conn, :index) will return http://subdomain.example.com/some-path, but instead of this I have http://example.com/some-path and this means that the function returns incorrect URL.

So, it would be great if we'll have something like: page_url(conn, :index, host: "subdomain.") it should works also in case when a subdomain is dynamical.

@TakteS That's a good solution. I usually have the resource that identifies the subdomain available when I want to link, so your suggestion is the most flexible and unbiased way I can think of.

@josevalim

This way you do it once and you don't need to remember about doing it per helper.

Right now there is a host field in the connection which can be just enough. I'm just not feeling good with parsing private.phoenix_endpoint.url into URI.

But then you will need to remember exactly which paths should be whitelisted - going on the opposite route.

Can you please elaborate more? Example will be helpful as well :)

@lessless I meant as something you would do in your pipeline or call in your controller:

conn = put_router_url(conn, url_string_or_struct)

And we will use that one for all routes generated by that connection.

It would be nice if Plug could include a subdomains field.
We're already setting the host field as @lessless mentioned. Plug could parse the incoming URL and compare it against the value in host and it includes the string before the host value as a subdomain.
Example:

config host = "domain.com"

incoming URL = "sub.domain.com"
# regex magic
conn.host # => domain.com
conn.subdomains # => ["sub"]

incoming URL = "domain.com"
# regex magic
conn.host # => domain.com
conn.subdomains # => [] or nil

incoming URL = "deep.sub.domain.com"
# regex magic
conn.host # => domain.com
conn.subdomains # => ["deep", "sub"]

The helpers then could accept a subdomains option which defaults to conn.subdomains and constructs the URL from that. I'm sure I'm missing some details though.

I'm using a similar approach in one of my projects to detect the subdomain, but I'm building the full URLs manually.

I still think that subsomain information should be stored in the Plug.Conn,
because of it's not Phoenix specific.
Thus url helpers should take it from conn instead of accepting as a param.

On Aug 27, 2017 21:28, "Abdullah Esmail" notifications@github.com wrote:

It would be nice if Plug could include a subdomains field.
We're already setting the host field as @lessless
https://github.com/lessless mentioned. Plug could parse the incoming
URL and compare it against the value in host and it includes the string
before the host value as a subdomain.
Example:

config host = "domain.com"

incoming URL = "sub.domain.com"

regex magic

conn.host # => domain.com
conn.subdomains # => ["sub"]

incoming URL = "domain.com"

regex magic

conn.host # => domain.com
conn.subdomains # => [] or nil

incoming URL = "deep.sub.domain.com"

regex magic

conn.host # => domain.com
conn.subdomains # => ["deep", "sub"]

The helpers then could accept a subdomains option which defaults to
conn.subdomains and constructs the URL from that. I'm sure I'm missing
some details though.

I'm using a similar approach in one of my projects to detect the
subdomain, but I'm building the full URLs manually.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/phoenixframework/phoenix/issues/2462#issuecomment-325201718,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABd9zCjg1o-yxuMCDQBwtUu17yJuPLsCks5scX0PgaJpZM4PDbSL
.

I'm am with @josevalim that if we support something like this, it should be done with a plug you can call rather than something automatic, which would be a breaking change and not necessarily what you want in all cases.

FWIW, while addressing this issue, I would encourage us to consider the host suffix functioanlity mentioned in the related #489 issue. In that issue @josevalim stated

it is quite hard to optimize suffix ones (but it can be done). :)

The reason I'm mentioning that, is that it's an issue that comes up when routing either involves differnt domains or (more commonly) subdomains for dev, test and prod workflows. For instance, if you have a router scope for dev.example.com, test.example.com and live.example.com it would be helpful to be able to match/route of the suffix instead of the prefix (as is common in some other frameworks. For instance:

scope "/", MyApp, host: "*.examplec.com" do
    pipe_through :browser

    get "/some-path", PageController, :index
end

Such that the scope can route of the *. wilcard for the example.com domain.

I've researched this, and didn't see an easily/readily available solution to do this in Elixir/Phoenix (apologies if I missed that). I've also seen this feature requested in a few placed, such as in the comments to this blog by @Gazler (I also asked @Gazler about it at ElixirConf, and he mentioned he didn't think the functionality exisited OOB right now).

I've done some work on this in other frameworks, so would be happy to help work on a PR and docs/guide if this was something @chrismccord or @josevalim wanted to support. If not, then I'll work on creating an alternate/custom solution. If warranted/appropriate, I can also open a separate issue.

I don't think we can properly optimized such cases, but plug is there to save you when you need special splat handling

Was this page helpful?
0 / 5 - 0 ratings