Play Framework: How to Host multiple sites using same Play instance (aka virtualhosts)
Lately I have coded few small services/sites using play framework and I found the need to co-host these multiple sites on a single physcial server.
The standard solution in play community is to run these play applications separately (each listening on a separate port) and then use apache or nginx or some other lightweight server as reverse proxy in front for routing (e.g., apache as front-end for play apps). It works, but it is too much overhead. Instead I wanted a single play instance running multiple sites -- similar to Apache virtualhost. Here is a quick guide on how I implemented this using Play framework (2.5.x).
Use Case - Single play instance serving two sites/domains say www.example1.com and www.example2.com
Solution -- in three easy steps:
Step 1: Create multiple route files one for each site
Each route file is in standard play format
# ls conf/
application.conf
prod.conf
example1.routes
example2.routes
Step 2: Extend DefaultHttpRequestHandler
We extend DefaultHttpRequestHandler to route requests to the right routes
file based on the host/domain name in the incoming request.
Notes:
- Every route file created under
conf
folder can be referred using its auto-generated reference - e.g., example1.Routes and example2.Routes for our case. - We use dependency injection (DI) to make our life easier
Sample code using scala:
package utils
import javax.inject._
import play.api.mvc.RequestHeader
import play.api.http.HttpConfiguration
import play.api.http.DefaultHttpRequestHandler
import play.api.http.HttpErrorHandler
import play.api.http._
class MultiSiteRequestHandler @Inject() (errorHandler: HttpErrorHandler,
configuration: HttpConfiguration,
filters: HttpFilters,
example1Router: example1.Routes,
example2Router: example2.Routes)
extends DefaultHttpRequestHandler(example1Router, errorHandler, configuration, filters) {
override def routeRequest(request: RequestHeader) = {
request.host match {
case "www.example1.com" => example1Router.routes.lift(request)
case "www.example2.com" => example2Router.routes.lift(request)
case _ => {
super.routeRequest(request)
}
}
}
}
Step 3: Tell play framework
We can tell play framework to use our newly defined class for request handling using following config:
# in application.conf
play.http.requestHandler = "utils.MultiSiteRequestHandler"
That's it and play is ready to host multiple sites using a single instance. We can add as many sites as we want using this approach