Skip to content

Instantly share code, notes, and snippets.

@bblfish
Last active December 20, 2015 00:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bblfish/6041149 to your computer and use it in GitHub Desktop.
Save bblfish/6041149 to your computer and use it in GitHub Desktop.
simple CORSProxy with Play2.2 - does not quite work. It blocks the HTTP connection, even though the URL is correctly fetched...
/*
* Copyright 2012 Henry Story, http://bblfish.net/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.www.readwriteweb.play
import play.api.mvc._
import akka.util.Timeout
import scala.concurrent.{ExecutionContext, Future, Promise}
import play.api.mvc.SimpleResult
import play.api.mvc.ResponseHeader
import akka.actor.ActorSystem
import play.api.libs.iteratee._
import play.api.libs.ws.WS
/**
* A <a href="http://www.w3.org/TR/cors/">CORS</a> proxy that allows a client to fetch remote RDF
* resources that do not have the required CORS headers.
*
* Currently this only permits GET operations. It is unclear what PUT, POST, DELETE operations would
* look like for a CORS proxy.
*
* note: graphSelector used to be an IterateeSelector[Rdf#Graph], should try to get write non blocking parsers
*/
class CORSProxy extends Controller {
implicit val system = ActorSystem("MySystem")
implicit val executionContext = scala.concurrent.ExecutionContext.global //todo: make sure this is right
// turn a header map into an (att,val) sequence
private implicit def sequentialise(headers: Map[String,Seq[String]]) = headers.toSeq.flatMap(pair=>pair._2.map(v=>(pair._1,v)))
def get(url: String) = EssentialAction { request =>
System.out.println("in CORSProxy.get(" + url + ")")
implicit val timeout = Timeout(10 * 1000)
val res: Iteratee[Array[Byte],SimpleResult] = Iteratee.ignore[Array[Byte]].mapM { Unit =>
val resultPromise = Promise[SimpleResult]()
WS.url(url) //todo: develop a WS for each client, so that one can separate their views
.withHeaders(request.headers.toSimpleMap.toSeq: _*) //todo: this looses headers, must fix WS.withHeaders method
.get { response =>
println("in get received headers =" + response.headers)
val code = if (response.status == 200) 203
else response.status
val hdrs = response.headers.map{case (key,value)=>(key,value.head)} //todo: we loose info here too!
val corsHeaders = if (!hdrs.contains("Access-Control-Allow-Origin")) {
val origin = hdrs.get("Origin")
hdrs + ("Access-Control-Allow-Origin" -> origin.getOrElse("*")) - ("Content-Length")
} else {
hdrs
}
println("will send the following headers to client for cors "+corsHeaders)
val (getIteratee, actionResultEnum) = joined[Array[Byte]]
val print = Enumeratee.map[Array[Byte]] { in =>
println(new String(in))
in
}
resultPromise.trySuccess(SimpleResult(ResponseHeader(code, corsHeaders), actionResultEnum.through(print)))
getIteratee
}
println("WS.url done, returing resultPromise.future")
resultPromise.future
}
println("calculated res: Iteratee[Array[Byte,SimpleResult]]")
res
}
/**
* Create a joined iteratee enumerator pair.
* found in http://jazzy.id.au/default/2013/06/12/call_response_websockets_in_play_framework.html
*
* When the enumerator is applied to an iteratee, the iteratee subsequently consumes whatever the iteratee in the pair
* is applied to. Consequently the enumerator is "one shot", applying it to subsequent iteratees will throw an
* exception.
*/
def joined[A]: (Iteratee[A, Unit], Enumerator[A]) = {
val promisedIteratee = Promise[Iteratee[A, Unit]]()
val enumerator = new Enumerator[A] {
def apply[B](finalIteratee: Iteratee[A, B]): Future[Iteratee[A,B]] = {
val doneIteratee = Promise[Iteratee[A, B]]()
// Equivalent to map, but allows us to handle failures
def wrap(delegate: Iteratee[A, B]): Iteratee[A, B] = new Iteratee[A, B] {
def fold[C](folder: (Step[A, B]) => Future[C])(implicit ec: ExecutionContext): Future[C] = {
print("in a wrapped fold --> ")
val toReturn = delegate.fold {
case done @ Step.Done(a, in) => {
println(s"Done($a,$in)")
doneIteratee.success(done.it)
folder(done)
}
case Step.Cont(k) => {
println(s"Cont($k)")
folder(Step.Cont(k.andThen(wrap)))
}
case err @ Step.Error(msg,in)=> {
println(s"Err($msg, $in)")
doneIteratee.failure(new Exception(msg))
folder(err)
}
}
toReturn.onFailure {
case e => doneIteratee.failure(e)
}
toReturn
}
}
if (promisedIteratee.trySuccess(wrap(finalIteratee).map(_ => ()))) {
doneIteratee.future
} else {
throw new IllegalStateException("Joined enumerator may only be applied once")
}
}
}
(Iteratee.flatten(promisedIteratee.future), enumerator)
}
}
@bblfish
Copy link
Author

bblfish commented Jul 21, 2013

Oh dear. The answer was really simple.
I was not removing the Content-Lenght header! I had checked that but ended up putting on the wrong
part of the if clause.

This works:

val corsHeaders = if (!hdrs.contains("Access-Control-Allow-Origin")) {
       hdrs + ("Access-Control-Allow-Origin" -> request.headers.get("Origin").getOrElse("*"))
    } else {
       hdrs
    } - ("Content-Length")

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