code-small.jpg

Control-Allow-Origin in Spray

Posted by Jan Machacek

Find me on:

Thu, Mar 21, 2013

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