Reputation: 805
I have a Play 2.0.1 application that I want to call using Javascript hosted on other domains. My Javascript call is failing with:
Origin http://mydomain.com is not allowed by Access-Control-Allow-Origin.
I have found a number of examples of how to set the correct HTTP header in Play 1, but have not found anything for Play 2.0.1. After reading the documentation (http://www.playframework.org/documentation/2.0.2/JavaResponse) I've tried the following just to get things working:
public static Result myJsonWebService() {
...
response().setHeader("Access-Control-Allow-Origin", "*");
return ok(toJson(jsonObject));
}
but my JS web service call is still failing.
What do I need to do to get this working?
Upvotes: 13
Views: 10312
Reputation: 95
Implemented as a Scala Filter (play 2.2.x):
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.mvc._
import play.api.mvc.Results._
import play.api.http.HeaderNames._
object Cors extends Filter {
def apply(next: (RequestHeader) => Future[SimpleResult])(request: RequestHeader): Future[SimpleResult] = {
val origin = request.headers.get(ORIGIN).getOrElse("*")
if (request.method == "OPTIONS") {
val response = Ok.withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> origin,
ACCESS_CONTROL_ALLOW_METHODS -> "POST, GET, OPTIONS, PUT, DELETE",
ACCESS_CONTROL_MAX_AGE -> "3600",
ACCESS_CONTROL_ALLOW_HEADERS -> s"$ORIGIN, X-Requested-With, $CONTENT_TYPE, $ACCEPT, $AUTHORIZATION, X-Auth-Token",
ACCESS_CONTROL_ALLOW_CREDENTIALS -> "true"
)
Future.successful(response)
} else {
next(request).map {
res => res.withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> origin,
ACCESS_CONTROL_ALLOW_CREDENTIALS -> "true"
)
}
}
}
}
object Global extends WithFilters(Cors) {...}
Upvotes: 1
Reputation: 1287
Just for Scala guys, this is the implementation I'm currently using:
import play.api.mvc._
import scala.concurrent._
import play.api.http.HeaderNames._
/**
* Action decorator that provide CORS support
*
* @author Giovanni Costagliola, Nick McCready
*/
case class WithCors(httpVerbs: String*)(action: EssentialAction) extends EssentialAction with Results {
def apply(request: RequestHeader) = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
val origin = request.headers.get(ORIGIN).getOrElse("*")
if (request.method == "OPTIONS") { // preflight
val corsAction = Action {
request =>
Ok("").withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> origin,
ACCESS_CONTROL_ALLOW_METHODS -> (httpVerbs.toSet + "OPTIONS").mkString(", "),
ACCESS_CONTROL_MAX_AGE -> "3600",
ACCESS_CONTROL_ALLOW_HEADERS -> s"$ORIGIN, X-Requested-With, $CONTENT_TYPE, $ACCEPT, $AUTHORIZATION, X-Auth-Token",
ACCESS_CONTROL_ALLOW_CREDENTIALS -> "true")
}
corsAction(request)
} else { // actual request
action(request).map(res => res.withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> origin,
ACCESS_CONTROL_ALLOW_CREDENTIALS -> "true"
))
}
}
}
To use it just decorate your action in the following way:
def myAction = WithCors("GET", "POST") {
Action { request =>
???
}
}
Upvotes: 14
Reputation: 4699
A nice way of doing this is by extending the Actions:
package actions;
import play.*;
import play.mvc.*;
import play.mvc.Http.Context;
import play.mvc.Http.Response;
public class CorsAction extends Action.Simple {
public Result call(Context context) throws Throwable{
Response response = context.response();
response.setHeader("Access-Control-Allow-Origin", "*");
//Handle preflight requests
if(context.request().method().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Auth-Token");
response.setHeader("Access-Control-Allow-Credentials", "true");
return ok();
}
response.setHeader("Access-Control-Allow-Headers","X-Requested-With, Content-Type, X-Auth-Token");
return delegate.call(context);
}
}
Then in your controllers add this line:
@With(CorsAction.class)
Then all ok() requests will respond with the above headers.
Upvotes: 3
Reputation: 805
Here is some background information...
So here is what I implemented:
As not-so-simple-requests (see 1 above) make a pre-flight call, you need to add the following to the routes file:
POST /url_to_json_webservice controllers.myJsonWebServices.myJsonWebService
OPTIONS /url_to_json_webservice controllers.myJsonWebServices.checkPreFlight
and then set up the following method in your controller:
public static Result checkPreFlight() {
response().setHeader("Access-Control-Allow-Origin", "*"); // Need to add the correct domain in here!!
response().setHeader("Access-Control-Allow-Methods", "POST"); // Only allow POST
response().setHeader("Access-Control-Max-Age", "300"); // Cache response for 5 minutes
response().setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); // Ensure this header is also allowed!
return ok();
}
Note i may have set more headers than needed here, so please check which ones you need!!
Also remember to add the following to the end of your controller method that returns the actually JSon result (as in my question above):
public static Result myJsonWebService() {
...
response().setHeader("Access-Control-Allow-Origin", "*");
return ok(toJson(jsonObject));
}
Upvotes: 11