Last active
December 20, 2015 19:38
-
-
Save jroper/6184296 to your computer and use it in GitHub Desktop.
Transparent HEAD request handling in Play 2.1. Note that it may be more efficient to implement HEAD support in an action directly, as this may avoid unnecessary opening of resources or rendering things.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import play.api.libs.iteratee.{Done, Iteratee, Enumerator} | |
import play.api.mvc._ | |
import play.api._ | |
import play.api.libs.concurrent.Execution.Implicits._ | |
object Global extends GlobalSettings { | |
override def onRouteRequest(req: RequestHeader) = { | |
// Lookup handler | |
super.onRouteRequest(req) match { | |
// If none was found and this is a HEAD request | |
case None if req.method == "HEAD" => { | |
// Try to look an action up again, but this time pretend it was a GET request | |
super.onRouteRequest(req.copy(method = "GET")) match { | |
// We found one, wrap it in a new action | |
case Some(wrapped: EssentialAction) => Some(new EssentialAction() with RequestTaggingHandler { | |
def apply(req: RequestHeader) = { | |
def skipBody(result: Result): Result = result match { | |
case SimpleResult(header, body) => { | |
// Tell the body enumerator it's done so that it can clean up resources | |
body(Done(())) | |
// Create a copy of the result, but with no content | |
SimpleResult(header, Enumerator(Results.EmptyContent())) | |
} | |
case ChunkedResult(header, body) => { | |
// Same as for SimpleResult | |
body(Done(())) | |
ChunkedResult(header, (it: Iteratee[Array[Byte], Unit]) => it.run) | |
} | |
// Unwrap | |
case AsyncResult(future) => AsyncResult(future.map(skipBody)) | |
} | |
// Invoke the wrapped action and skip the body it returns | |
wrapped(req).map(skipBody) | |
} | |
// Ensure that request tags are added if necessary | |
def tagRequest(request: RequestHeader) = wrapped match { | |
case tagging: RequestTaggingHandler => tagging.tagRequest(request) | |
case _ => request | |
} | |
}) | |
case other => other | |
} | |
} | |
case other => other | |
} | |
} | |
} |
Beware unintended consequences.
Although GET is safe and idempotent at the protocol level, there are other side-effects that may not be desired if the request was for HEAD.
I recommend making this something that can be explicitly turned on through configuration for those who want it as the default for all routes, and also reusable for those who may want this behaviour but only on selected routes.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice, I needed this recently and was thinking to build a wrapper like this myself :)