Hi Play! dev community,
I would like to propose a new feature for Play! to allow for relative controller endpoint and asset routing within a webapp. This feature will make deployment of Play! apps much easier in environments where infrastructure (i.e. webservers, load balancers) cannot be configured. Instead of writing rewrite rulesets or calculating relative paths by hand, you can just turn on a configuration in Play! and deploy your app. I found several threads on the play-users mailing list, as well as stack overflow, where this request has been made, but that didn't have a satisfactory conclusion.
In my case, I experienced this issue using the DC/OS admin router, which is basically just an nginx process on a publicly facing node of a DC/OS cluster that dynamically rewrites requests to internally hosted services. From the admin router's README:
When your container/task has its own IP (typically when running in a virtual network), http://<dcos-cluster>/service/<application ID> is forwarded to the container/task's IP
...
The endpoint should only use relative links for links and referenced assets such as .js and .css files. This is due to the fact that the linked resources will be reachable only in their relative location <dcos-cluster>/services/<application ID><link>.
The `play.http.context` configuration is brittle because it assumes that webserver rewrite rules are configured properly. In situations where a load balancer or webserver is simply rewriting traffic to the root of the destination web app server, then `play.http.context` will not help.
Example)
Consumer facing webserver configured to rewrite traffic to Play! app on route: /service/myapp
Play! configured with `play.http.context`: /
- Request made to consumer facing webserver
http://acme.com/service/myapp
- Webserver rewrites request to Play! app
http://10.10.10.10/
- Play! app serves root route to end user
- End user's browser attempts to resolve an asset (controller endpoint link, static asset, etc.)
/public/bootstrap.css - Webserver requests
http://acme.com/public/bootstrap.css - Webserver never rewrites request to Play! app
Webserver returns incorrect asset or 404
With Play! configured with `play.http.context`: /service/myapp
- Request made to consumer facing webserver
http://acme.com/service/myapp
- Webserver rewrites request to Play! app
http://10.10.10.10/
- Play! app returns 404 because there are no routes available at a higher level then defined by `play.http.context`
There are many ways to work around this problem. It could be handled on the client or server side, but I think the most sane solution would be provide a new method or overload in the reverse routing code generated by Play! framework. When requesting a controller endpoint or asset, a relative route could be calculated given the current route the user is visiting. In the above example, when visiting the root route of the play app the emitted page would generate a relative path to bootstrap.css instead of an absolute one (`public/bootstrap.css` vs `/public/bootstrap.css`).
Relative route generation examples:
Requested: /public/bootstrap.css
- Current route: /
Emitted route: ./public/bootstrap.css - Current route: /foobar
Emitted route: ./public/bootstrap.css - Current route: /foobar/
Emitted route: ./../public/bootstrap.css - Current route: /foobar/baz/
Emitted route: ./../../public/bootstrap.css
Requested: /foobar
- Current route: /
Emitted route: ./foobar - Current route: /foobar/baz
Emitted route: ./../foobar
Requested: /
- Current route: /
Emitted route: ./ - Current route: /foobar
Emitted route: ./ - Current route: /foobar/baz
Emitted route: ./../
Given the current context of the request (the actual URL to the view in the process of being rendered), you can calculate the relative URL of any other asset or link in the page. My workaround solution was to create an implicit type definition on `Call` to add a `relative` method which accepted an implicit `Request`. Then I included the implicit Request in any view I needed to make calls to the reverse router. Here's a gist that summarizes my impl.
Based on feedback to this proposal I would be more than happy to contribute a PR to provide a similar capability within the framework itself.
Regards,
Sean
--
Senior Software Engineer, Lightbend, Inc.