Skip to content

Instantly share code, notes, and snippets.

@joseraya
Created July 1, 2014 21:24
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save joseraya/176821d856b43b1cfe19 to your computer and use it in GitHub Desktop.
Save joseraya/176821d856b43b1cfe19 to your computer and use it in GitHub Desktop.
CORS directive for Spray
package com.agilogy.spray.cors
import spray.http.{HttpMethods, HttpMethod, HttpResponse, AllOrigins}
import spray.http.HttpHeaders._
import spray.http.HttpMethods._
import spray.routing._
// see also https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
trait CORSSupport {
this: HttpService =>
private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins)
private val optionsCorsHeaders = List(
`Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent"),
`Access-Control-Max-Age`(1728000))
def cors[T]: Directive0 = mapRequestContext { ctx => ctx.withRouteResponseHandling({
//It is an option requeset for a resource that responds to some other method
case Rejected(x) if (ctx.request.method.equals(HttpMethods.OPTIONS) && !x.filter(_.isInstanceOf[MethodRejection]).isEmpty) => {
val allowedMethods: List[HttpMethod] = x.filter(_.isInstanceOf[MethodRejection]).map(rejection=> {
rejection.asInstanceOf[MethodRejection].supported
})
ctx.complete(HttpResponse().withHeaders(
`Access-Control-Allow-Methods`(OPTIONS, allowedMethods :_*) :: allowOriginHeader ::
optionsCorsHeaders
))
}
}).withHttpResponseHeadersMapped { headers =>
allowOriginHeader :: headers
}
}
}
val routes: Route =
cors {
path("hello") {
get {
complete {
"GET"
}
} ~
put {
complete {
"PUT"
}
}
}
}
@oreganrob
Copy link

Hi,

I've been trying to get the CORSSupport class to work with Basic Authentication but I'm not getting anywhere fast.
I've added Authorization to the list of supported classes but with my implementation the CORSSupport class doesn't seem to get called at all. It seems almost like the Authorization response is handled separately and never executes the CORS header class as the result I always get back is a 401 with the message that no origin header was present in the response.
Does anyone have any pointers on how to use CORS with Basic Auth?

My route looks like follows...

pathPrefix("v1") {
cors {
pathPrefix("test") {
authenticate(BasicAuth(ClientAuthentication, "API Authentication")) {
customerProfile =>
get {
respondWithMediaType(application/json){
complete("""{"result": "test!"}""")
}
}
}
}
}

Thanks

@oreganrob
Copy link

Ignore above comment. Got it working by adding the following to the CORSSupport class...

|| (ctx.request.method.equals(HttpMethods.OPTIONS)
&& x.exists(_.isInstanceOf[AuthenticationFailedRejection]))

as well as adding "Authorization" to the list of headers

Thanks

@OElesin
Copy link

OElesin commented Sep 27, 2015

This is my implementation:

class MyServiceActor extends Actor with MyService {

def actorRefFactory = context
def requestMethodAndResponseStatusAsInfo(req: HttpRequest): Any => Option[LogEntry] = {
case res: HttpResponse => Some(LogEntry(req.method + ":" + req.uri + ":" + res.message.status, InfoLevel))
case _ => None // other kind of responses
}

def routeWithLogging = logRequestResponse(requestMethodAndResponseStatusAsInfo _)(myRoute ~ testRoute)
def receive = runRoute(routeWithLogging)

}

// this trait defines our service behavior independently from the service actor
trait MyService extends HttpService {

implicit val rejectHandler = RejectionHandler {
case MissingQueryParamRejection(paramName) :: _ => ctx
=> ctx.complete(APIresponse.errorResponse(s"Parameter ' $paramName ' is missing", 404))
case MissingFormFieldRejection(paramName) :: _ => ctx
=> ctx.complete(APIresponse.errorResponse(s"Parameter ' $paramName ' is missing", 404))
}

private val allowOriginHeader = Access-Control-Allow-Origin(AllOrigins)
private val optionsCorsHeaders = List(
Access-Control-Allow-Headers("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent"),
Access-Control-Max-Age(1728000))

def cors[T]: Directive0 = mapRequestContext { ctx => ctx.withRouteResponseHandling({
  //It is an option requeset for a resource that responds to some other method
  case Rejected(x) if (ctx.request.method.equals(HttpMethods.OPTIONS) && !x.filter(_.isInstanceOf[MethodRejection]).isEmpty) => {
    val allowedMethods: List[HttpMethod] = x.filter(_.isInstanceOf[MethodRejection]).map(rejection=> {
      rejection.asInstanceOf[MethodRejection].supported
    })
    ctx.complete(HttpResponse().withHeaders(
      `Access-Control-Allow-Methods`(OPTIONS, allowedMethods :_*) ::  allowOriginHeader ::
       optionsCorsHeaders
    ))
  }
}).withHttpResponseHeadersMapped { headers =>
  allowOriginHeader :: headers

}

}

val userLogic = new UserMgmtLOGIC
var response = new String

val myRoute: Route =
path("api" / "user-service") {
get {
ctx => ctx.complete(APIresponse.successResponese(null, "Welcome to User Management Service"))
}
} ~ cors {
path("api" / "user-service" / "create-user") {
post {
formFields('email, 'user_name, 'password, 'role?) {(email, user_name, password, role) =>
if(email.isEmpty()){
ctx => ctx.complete(APIresponse.errorResponse("Email field is empty"))
}else if(password.isEmpty()){
ctx => ctx.complete(APIresponse.errorResponse("Password field is empty"))
}else if(user_name.isEmpty()){
ctx => ctx.complete(APIresponse.errorResponse("User Name field is empty"))
}else{
val userData = Map("email" -> email, "user_name" -> user_name, "password" -> password, "role" -> role)
var payload = userData.asInstanceOf[Map[String, Any]]
response = userLogic.createUserRecord(payload)
ctx => ctx.complete(response)
}
}
}
}
} ~ path("api" / "user-service" / "delete-user") {
get {
parameter('email){(email) =>
response = userLogic.deleteUserRecord(email)
ctx => ctx.complete(response)
}
}
} ~ path("api" / "user-service" / "all-users") {
get {
response = userLogic.getAllUsers()
ctx => ctx.complete(response)
}
} ~
cors {
path("api" / "user-service" / "login") {
post {
formFields('username, 'password) {(username, password) =>
if(username.isEmpty()){
ctx => ctx.complete(APIresponse.errorResponse("Email field is empty"))
}else if(password.isEmpty()){
ctx => ctx.complete(APIresponse.errorResponse("Password field is empty"))
}else{
response = userLogic.userLogin(username, password)
ctx => ctx.complete(response)
}
}
}
}
} ~ path("api" / "user-service" / "test") {
get {
respondWithMediaType(application/json) {
respondWithHeader(RawHeader("Access-Control-Allow-Origin","*")) {
ctx => ctx.complete("""{"name" : "olalekan"}""")
}
}
}
}
}

And I keep getting this
screen shot 2015-09-27 at 11 50 15 am

Can anyone help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment