Created
March 10, 2011 23:58
-
-
Save mckamey/865216 to your computer and use it in GitHub Desktop.
Servlet Filter for JAX-RS content negotiation which fixes WebKit Accept header and adds extension support
This file contains 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
package org.example.www.filters; | |
import java.io.IOException; | |
import java.util.*; | |
import javax.servlet.*; | |
import javax.servlet.http.*; | |
/** | |
* Modifies Accept headers and allows URL extensions to improve JAX-RS content negotiation | |
* Adds "Vary: accept" header to response | |
* | |
* Adapted from: http://www.zienit.nl/blog/2010/01/rest/control-jax-rs-content-negotiation-with-filters | |
*/ | |
public class AcceptFilter implements Filter { | |
private final Map<String,String> extensions = new HashMap<String,String>(); | |
@SuppressWarnings("unchecked") | |
public void init(FilterConfig config) throws ServletException { | |
Enumeration<String> exts = config.getInitParameterNames(); | |
while (exts.hasMoreElements()) { | |
String ext = exts.nextElement(); | |
if (ext != null && !ext.isEmpty()) { | |
this.extensions.put("."+ext.toLowerCase(), config.getInitParameter(ext)); | |
} | |
} | |
} | |
public void destroy() {} | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |
HttpServletRequest httpRequest = (HttpServletRequest)request; | |
String uri = httpRequest.getRequestURI(); | |
String ext = this.getExtension(uri); | |
String accept = this.extensions.get(ext); | |
if (accept == null) { | |
// This workaround may no longer needed with the ";qs=2" answer | |
// http://stackoverflow.com/questions/5250923/http-content-negotiation-conflicts-in-jax-rs-jersey/5536837#5536837 | |
accept = httpRequest.getHeader("accept"); | |
if (accept != null && accept.indexOf("text/html") > 0) { | |
// patch WebKit-style Accept headers by elevating "text/html" | |
accept = "text/html,"+accept; | |
request = new RequestWrapper(httpRequest, uri, accept); | |
} | |
} else { | |
// remove extension and remap the Accept header | |
uri = uri.substring(0, uri.length() - ext.length()); | |
request = new RequestWrapper(httpRequest, uri, accept); | |
} | |
// add "Vary: accept" to the response headers | |
HttpServletResponse httpResponse = (HttpServletResponse)response; | |
httpResponse.addHeader("Vary", "accept"); | |
chain.doFilter(request, response); | |
} | |
private String getExtension(String path) { | |
int index = path.lastIndexOf('.'); | |
if (index < 0 || path.lastIndexOf('/') > index) { | |
return ""; | |
} | |
return path.substring(index); | |
} | |
private static class RequestWrapper extends HttpServletRequestWrapper { | |
private final String uri; | |
private final String accept; | |
public RequestWrapper(HttpServletRequest request, String uri, String accept) { | |
super(request); | |
this.uri = uri; | |
this.accept = accept; | |
} | |
@Override | |
public String getRequestURI() { | |
return this.uri; | |
} | |
@Override | |
public Enumeration getHeaders(String name) { | |
if (!"accept".equalsIgnoreCase(name)) { | |
return super.getHeaders(name); | |
} | |
Vector<String> values = new Vector<String>(1); | |
values.add(this.accept); | |
return values.elements(); | |
} | |
} | |
} |
This file contains 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
<?xml version="1.0" encoding="UTF-8"?> | |
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" | |
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> | |
<filter> | |
<filter-name>accept-filter</filter-name> | |
<filter-class>org.example.www.filters.AcceptFilter</filter-class> | |
<init-param> | |
<param-name>html</param-name> | |
<param-value>text/html</param-value> | |
</init-param> | |
<init-param> | |
<param-name>xml</param-name> | |
<param-value>application/xml</param-value> | |
</init-param> | |
<init-param> | |
<param-name>json</param-name> | |
<param-value>application/json</param-value> | |
</init-param> | |
</filter> | |
<filter-mapping> | |
<filter-name>accept-filter</filter-name> | |
<url-pattern>/*</url-pattern> | |
</filter-mapping> | |
</web-app> |
@asantosca, that's odd. That line is specifically checking that "text/html" is not at the start. Changing it to > -1
or >= 0
would add a second "text/html, " at the beginning.
What is the "Accept" header that it is failing for you?
I was writing a long email with all info I could gather when it hit
me. Yes, he is correct.
The problem was with my code. I added a else when the Accept wasn't
defined. My requirement is:
Only JSON or XML are acceptable, but if no Accept was defined then
JSON should be the default.
So my code was checking if there was no Accept and setting the Accept
as JSON... even when the Accept was "text/html".
Thank you for finding the bug and sorry for the disruption.
…On Fri, Apr 13, 2012 at 10:57 AM, Stephen McKamey ***@***.*** wrote:
Hmm, that's odd. That line is specifically checking that "text/html" is _not_ at the start. Changing it to `> -1` or `>= 0` would add a second "text/html, " at the beginning.
What is the "Accept" header that it is failing for you?
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/865216
@asantosca, no worries! Glad it has been useful.
At line 52 you have "Very"... when comment says "Vary".. IIRC comment is correct!
Also I'd not, prefix extensions hashmap key with dots and convert returned ext to lowercase without the dot, so .XML or .xml would work...
ie.
Line 24:
this.extensions.put(ext.toLowerCase(), config.getInitParameter(ext));
Line 46:
uri = uri.substring(0, uri.length() - ext.length()-1);
private String getExtension(String path) {
String result = "";
int index = path.lastIndexOf('.');
if (!(index < 0 || path.lastIndexOf('/') > index)) {
result = path.substring(index+1).toLowerCase();
}
return result;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is failing for me if text/html is the first accepted header.
To fix it, I changed the code from
if (accept != null && accept.indexOf("text/html") > 0) {
to
if (accept != null && accept.indexOf("text/html") > -1) {
Thank you for the code.