Sergio Daniel
Sergio Daniel

Reputation: 116

Play Framework 2.3 - CORS Headers

UPDATE the new Play 2.5 offers a new CORS Filter

As the new 2.3 Java version finished the migration of the Response class to Promise class the following code no longer works.

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"); 
   response.setHeader("Access-Control-Allow-Origin", "*"); 
 return ok() 

 } 

 response.setHeader("Access-Control-Allow-Headers","X-Requested-With, Content-Type, X-    Auth-Token"); 
 return delegate.call(context); 
 } 
}

I am developing an application in Play (Java) 2.3 and I have looked and tried different methods to enable CORS -including adding /OPTIONS methods to the routes file- without success.

I would much appreciate some light on how the new Response implementation would handle this type of interception, because it seems not to have any effects in the headers when implemented in the new Promise class.

Thanks in advance for all the help!!

Upvotes: 6

Views: 11745

Answers (5)

cyril
cyril

Reputation: 1005

for me configuration working in chrome, firefox and explorer :

Play Framework CORS Headers

Upvotes: 0

Phillip Kigenyi
Phillip Kigenyi

Reputation: 1489

This worked for me. However you may need to tweak some parameters(application.conf)

Step 1 Create filters class(UrlFilter)

package filters;

    import play.mvc.EssentialFilter;
    import play.filters.cors.CORSFilter;
    import play.http.DefaultHttpFilters;

    import javax.inject.Inject;

    public class UrlFilter extends DefaultHttpFilters {
        @Inject public UrlFilter(CORSFilter corsFilter) {
            super(corsFilter);
        }
    }

Step 2 import filters package in build.sbt

libraryDependencies += filters

Step 3 Register the UrlFilter class in application.conf

play.http.filters = "filters.UrlFilter"

NOTE: Remember to point to its exact location. Mine was in app/filters/UrlFilter.java

Step 4 Add the actual configurations that allow "access control allow origin" and many more. To the application.conf

 play.filters.cors {

  # The path prefixes to filter.
  pathPrefixes = ["/"]

  # The allowed origins. If null, all origins are allowed.
  allowedOrigins = null

  # The allowed HTTP methods. If null, all methods are allowed
  allowedHttpMethods = null

  # The allowed HTTP headers. If null, all headers are allowed.
  allowedHttpHeaders = null

  # The exposed headers
  exposedHeaders = []

  # Whether to support credentials
  supportsCredentials = true

  # The maximum amount of time the CORS meta data should be cached by the client
  preflightMaxAge = 1 hour
}

Credit https://www.playframework.com/documentation/2.5.x/CorsFilter

https://www.playframework.com/documentation/2.5.x/resources/confs/filters-helpers/reference.conf

Upvotes: 0

Tinus
Tinus

Reputation: 301

Cross post from enable-cors-in-java-play-framework-2-2-x.

The solutions proposed by @alexhanschke does not work when the request throws an exception (internal server error), because filters are not applied when that happens (see https://github.com/playframework/playframework/issues/2429). To solve that you have to wrap a scala class and return that as a result, as shown below in full. Please note that this still requires the options route specified and a controller to handle the options request.

See the entire thing here https://gist.github.com/tinusn/38c4c110f7cd1e1ec63f.

import static play.core.j.JavaResults.BadRequest;
import static play.core.j.JavaResults.InternalServerError;
import static play.core.j.JavaResults.NotFound;

import java.util.ArrayList;
import java.util.List;

import play.GlobalSettings;
import play.api.mvc.Results.Status;
import play.libs.F.Promise;
import play.libs.Scala;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import scala.Tuple2;
import scala.collection.Seq;

public class Global extends GlobalSettings {

  private class ActionWrapper extends Action.Simple {
    public ActionWrapper(Action<?> action) {
      this.delegate = action;
    }

    @Override
    public Promise<Result> call(Http.Context ctx) throws java.lang.Throwable {
      Promise<Result> result = this.delegate.call(ctx);
      Http.Response response = ctx.response();
      response.setHeader("Access-Control-Allow-Origin", "*");
      return result;
    }
  }

  /*
  * Adds the required CORS header "Access-Control-Allow-Origin" to successfull requests
  */
  @Override
  public Action<?> onRequest(Http.Request request, java.lang.reflect.Method actionMethod) {
    return new ActionWrapper(super.onRequest(request, actionMethod));
  }

  private static class CORSResult implements Result {
    final private play.api.mvc.Result wrappedResult;

    public CORSResult(Status status) {
      List<Tuple2<String, String>> list = new ArrayList<Tuple2<String, String>>();
      Tuple2<String, String> t = new Tuple2<String, String>("Access-Control-Allow-Origin","*");
      list.add(t);
      Seq<Tuple2<String, String>> seq = Scala.toSeq(list);
      wrappedResult = status.withHeaders(seq);
    }

    public play.api.mvc.Result toScala() {
      return this.wrappedResult;
    }
  }

  /*
  * Adds the required CORS header "Access-Control-Allow-Origin" to bad requests
  */
  @Override
  public Promise<Result> onBadRequest(Http.RequestHeader request, String error) {
    return Promise.<Result>pure(new CORSResult(BadRequest()));
  }

  /*
  * Adds the required CORS header "Access-Control-Allow-Origin" to requests that causes an exception
  */
  @Override
  public Promise<Result> onError(Http.RequestHeader request, Throwable t) {
    return Promise.<Result>pure(new CORSResult(InternalServerError()));
  }

  /*
  * Adds the required CORS header "Access-Control-Allow-Origin" when a route was not found
  */
  @Override
  public Promise<Result> onHandlerNotFound(Http.RequestHeader request) {
    return Promise.<Result>pure(new CORSResult(NotFound()));
  }

}

Upvotes: 1

cyberabis
cyberabis

Reputation: 330

Solved this by:

All API responses from the server should contain a header: “Access-Control-Allow-Origin”, “*”. We need to write a wrapper for all action responses.

In Global.java

import java.net.URL;

import play.*;
import play.libs.F.Promise;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;

public class Global extends GlobalSettings {

  // For CORS
  private class ActionWrapper extends Action.Simple {
    public ActionWrapper(Action<?> action) {
      this.delegate = action;
    }

    @Override
    public Promise<Result> call(Http.Context ctx) throws java.lang.Throwable {
      Promise<Result> result = this.delegate.call(ctx);
      Http.Response response = ctx.response();
      response.setHeader("Access-Control-Allow-Origin", "*");
      return result;
    }
  }

  @Override
  public Action<?> onRequest(Http.Request request,
      java.lang.reflect.Method actionMethod) {
    return new ActionWrapper(super.onRequest(request, actionMethod));
  }

}

Server requests like POST, PUT make a preflight request to the server before the main request. The response for these preflight requests should contain below headers:

“Access-Control-Allow-Origin”, “” “Allow”, “” “Access-Control-Allow-Methods”, “POST, GET, PUT, DELETE, OPTIONS” “Access-Control-Allow-Headers”, “Origin, X-Requested-With, Content-Type, Accept, Referer, User-Agent”

In routes add:

OPTIONS /*all                           controllers.Application.preflight(all)

In Application Coltroller:

package controllers;

import play.mvc.*;

public class Application extends Controller {

    public static Result preflight(String all) {
        response().setHeader("Access-Control-Allow-Origin", "*");
        response().setHeader("Allow", "*");
        response().setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
        response().setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Referer, User-Agent");
        return ok();
    }

}

PS: By this approach I did not have to create a scala filter for this.

Upvotes: 8

CaptainKerk
CaptainKerk

Reputation: 41

It looks like you may have solved your problem, but just for clarity...

You are pretty close...I believe you don't need to instantiate your own Response object, you can just call the one passed in via a method:

public Result call() throws Throwable
{     
    response().setHeader("Access-Control-Allow-Origin", "*"); 
       //Handle preflight requests 
    if(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"); 
        response().setHeader("Access-Control-Allow-Origin", "*"); 
        return ok();
    } 
    else
    {
          return badRequest();
    }
}

Hope that helps.

Upvotes: 2

Related Questions