Skip to content

Instantly share code, notes, and snippets.

@mckamey
Created March 10, 2011 23:58
Show Gist options
  • Save mckamey/865216 to your computer and use it in GitHub Desktop.
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
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();
}
}
}
<?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
Copy link

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.

@mckamey
Copy link
Author

mckamey commented Apr 13, 2012

@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?

@asantosca
Copy link

asantosca commented Apr 13, 2012 via email

@mckamey
Copy link
Author

mckamey commented Apr 13, 2012

@asantosca, no worries! Glad it has been useful.

@jgfet
Copy link

jgfet commented Nov 25, 2017

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