Reputation: 383
I have an app that requires I define an action for both the HEAD and GET verbs. Another app (Mozilla Open Badges) calls my app using two HTTP requests. It first uses HEAD to verify that my URL looks like it returns the correct type of response, then uses GET against the same URL to fetch the contents. The following approach works:
GET /assertion controllers.Assertion.index
HEAD /assertion controllers.Assertion.index
...this works, but is a DRY violation (Don't Repeat Yourself).
I'd prefer something like:
(GET, HEAD) /assertion controllers.Assertion.index
...but this is not allowed. I would also be happy if GET gave you HEAD for free, but there may be reasons that I don't understand to block HEAD for a GET action.
I suppose the redundant Path-to-Action definition isn't the end of the world, but I like to keep my code clean where possible.
Based on the W3C spec for HEAD (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html), I belive Play Framework 2 is behaving incorrecly for not allowing a HEAD call on a route defined for GET:
9.4 HEAD
The HEAD method is identical to GET except that the server MUST NOT return a
message-body in the response. The metainformation contained in the HTTP headers in
response to a HEAD request SHOULD be identical to the information sent in response
to a GET request. This method can be used for obtaining metainformation about the
entity implied by the request without transferring the entity-body itself. This
method is often used for testing hypertext links for validity, accessibility, and
recent modification.
Upvotes: 3
Views: 1301
Reputation: 55798
In Play 2.0+ there's not possible to use wildcard VERBs (like *
or ANY
or multivalued) (which could be a solution for you), so in this case 'DRY violation' you are using is only official way.
Edit
Actually you can try another approach:
routes
file put 'catch-all HEAD` rulezHEAD /path
'catch-all' sends it to autoHead(path: String)
action.GET
version of the route with WebServices, then get the response and return headers only.I added working sample of this approach (Java only) to play-simple-rest sample app. The things important for redirecting are:
public static Result autoHead(String originalPath) throws IllegalAccessException {
WS.WSRequestHolder forwardedRequest = WS.url("http://" + request().host() + request().path());
// this header will allow you to make additional choice i.e. avoid tracking the request or something else
// see condition in index() action
forwardedRequest.setHeader("X_FORWARD_FROM_HEAD", "true");
// Forward original headers
for (String header : request().headers().keySet()) {
forwardedRequest.setHeader(header, request().getHeader(header));
}
// Forward original queryString
for (String key : request().queryString().keySet()) {
for (String val : request().queryString().get(key)) {
forwardedRequest.setQueryParameter(key, val);
}
}
// Call the same path but with GET
WS.Response wsResponse = forwardedRequest.get().get();
// Set returned headers to the response
for (Field f : Http.HeaderNames.class.getFields()) {
String headerName = f.get(null).toString();
if (wsResponse.getHeader(headerName) != null) {
response().setHeader(headerName, wsResponse.getHeader(headerName));
}
}
return status(wsResponse.getStatus());
}
public static boolean forwardedFromHead() {
return (request().getHeader("X_FORWARD_FROM_HEAD") != null && "true".equals(request().getHeader("X_FORWARD_FROM_HEAD")));
}
And two routes HEAD on the end of the file route file
HEAD / controllers.Application.autoHead(originalPath:String ?= "/")
HEAD /*originalPath controllers.Application.autoHead(originalPath:String)
P.S. If you would like to write something similar in Scala it would be nice :)
Upvotes: 2