code-small.jpg

Cake Team Blogs

Control-Allow-Origin in Spray

Posted by Jan Machacek

Find me on:

21/03/13 08:46

It is 2013, we have modern browsers. And these browsers do not like to load resources from origin that's different from the resource being displayed. So, if you have a page at http://www.foo.com and want to make a request to http://api.bar.com, the browsers will (and should) reject it, unless you explicitly allow it.

We use the Access-Control-Allow-Origin HTTP header to do just that. Now, if you have a Spray route that uses the Spray JSON support to marshal the responses, you're a bit stuck. You have something like

...
path("transactions" / JavaUUID) { id: TransactionId =>
  get {
    complete {
       (actor ? Messsage(id)).mapTo[Response]
    }
  }
}

Now what? You still want to use the convenient marshallers (e.g. Spray JSON), but you want to specify the HTTP header. I have created the CrossLocationRouteDirectives, which lets you do just that. The only downside is that there is another block inside complete.

... with CrossLocationRouteDirectives {

  val origin = "http://www.foo.com"

  path("transactions" / JavaUUID) { id: TransactionId =>
    get {
      complete {
        fromObjectCross(origin) {
          (actor ? Messsage(id)).mapTo[Response]
        }
      }
    }
  }

This makes everything tick along quite nicely: you still have the marshalling infrastructure, and you can now allow the remote origin by setting the value of the Access-Control-Allow-Origin HTTP header. To skin the can it yet another way (a charming British saying!), you can now have some jQuery AJAX in your pages on http://www.foo.com that make requests to http://api.bar.com.

$.ajax({
    type: "GET",
    url: "http://api.bar.com/transactions/...",
}).done(function(data) {
   // ponies & unicorns
}).fail(function(jqXHR, textStatus, errorThrown) {
   // PHP!
});

The code of CrossLocationRouteDirectives is quite simply

trait CrossLocationRouteDirectives extends RouteDirectives {

  implicit def fromObjectCross[T: Marshaller](origin: String)(obj: T) =
    new CompletionMagnet {
      def route: StandardRoute =
        new CompletionRoute(OK,
              RawHeader("Access-Control-Allow-Origin", origin) :: Nil, obj)
    }

  private class CompletionRoute[T: Marshaller](status: StatusCode,
                                               headers: List[HttpHeader],
                                               obj: T)
    extends StandardRoute {

    def apply(ctx: RequestContext) {
      ctx.complete(status, headers, obj)
    }
  }
}

Topics: Spray, AJAX

Posts by Topic

see all

Subscribe to Email Updates