Hello,
as discussed two weeks ago, some browsers (e.g. Internet Explorer 7.0
and Firefox 2.0) sends as accepted media type XML with a higher quality
than HTML. The consequence is, that a HTTP server sends XML instead of
HTML, if it could produce XML.
For this problem I've created a Filter (named HtmlPreferer now), that
will increase the qualities for HTML media types (text/html and
application/xhtml+xml) higher than both XML types (text/xml and
application/xml), if at least one of both is available in a request.
Now it is available in the JAX-RS extension. What do you think about
make it available for all developers in the main project? I put the
actual source and a test case as attachment.
Jerome or Thierry: If you want to integrate the class in the main
package or where ever, use the file of the JAX-RS; perhaps I continue
developing or something like this.
best regards
Stephan
Rob Heittman schrieb:
This legacy browser behavior is frustrating in the extreme. Why in
heaven's name would a tool meant primarily for viewing HTML, request
XML as a higher quality representation? Just goes to show how
uber-excited everybody was about XML once upon a time. You know,
because in the future, all web pages will someday be XML with a
reference to an XSL stylesheet, not HTML.
Choosing a different MediaType for your XML, that the browser doesn't
ask for, is the usual solution.
Another workable solution I have found -- if you are using XML and
will get criticized for making up MIME types -- is to expose the
browser-friendly HTML variant by itself on a distinct URI (e.g.
person.html). That's sloppy too, just in a different way.
Now, packaging your data with JSON instead of XML will avoid the issue
altogether, without making up MIME types =)
- R
On 2/18/08, *Stephan Koops* <[EMAIL PROTECTED]
<mailto:[EMAIL PROTECTED]>> wrote:
if a browser requests to a REST server, some browsers (Firefox and IE
for example, Opera not) requests text/xml and application/xml with a
higher quality than text/html.
/*
* Copyright 2005-2008 Noelios Consulting.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License"). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* http://www.opensource.org/licenses/cddl1.txt See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at http://www.opensource.org/licenses/cddl1.txt If
* applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*/
package org.restlet.ext.jaxrs;
import java.util.List;
import java.util.Map;
import org.restlet.Context;
import org.restlet.Filter;
import org.restlet.Restlet;
import org.restlet.data.MediaType;
import org.restlet.data.Preference;
import org.restlet.data.Request;
import org.restlet.data.Response;
/**
* <p>
* Some browsers (e.g. Internet Explorer 7.0 and Firefox 2.0) sends as accepted
* media type XML with a higher quality than html. The consequence is, that a
* HTTP server sends XML instead of HTML, if it could produce XML. To avoid
* this, you can use this filter.
* </p>
* <p>
* This Filter will increase the qualities for HTML media types (text/html and
* application/xhtml+xml) higher than both XML types (text/xml and
* application/xml), if at least one of both is available in a request. The
* check is implemented in method [EMAIL PROTECTED]
#shouldChangeToPrefereHtml(Request)}.
* <br>
* Requests that not are not effected.
* </p>
* <p>
* You may alter the test if the filter should change the request by subclass
* this Filter and overrider method [EMAIL PROTECTED]
#shouldChangeToPrefereHtml(Request)}.
* </p>
*
* @author Stephan Koops
*/
public class HtmlPreferer extends Filter {
private static final int MT_PREF_APP_XHTML = 1;
private static final int MT_PREF_APP_XML = 3;
private static final int MT_PREF_TEXT_HTML = 0;
private static final int MT_PREF_TEXT_XML = 2;
private static final String MT_QUALITY_ARRAY =
"org.restlet.HtmlPreferer.qualities";
/**
* Creates a new [EMAIL PROTECTED] HtmlPreferer}. You should use constructor
* [EMAIL PROTECTED] #HtmlPreferer(Context)} or [EMAIL PROTECTED]
#HtmlPreferer(Context, Restlet)}.
*/
@Deprecated
public HtmlPreferer() {
super();
}
/**
* Creates a new [EMAIL PROTECTED] HtmlPreferer}. You can give also the
next restlet
* by using constructor [EMAIL PROTECTED] #HtmlPreferer(Context, Restlet)}.
*
* @param context
* the context from the parent
*/
public HtmlPreferer(Context context) {
super(context);
}
/**
* Creates a new [EMAIL PROTECTED] HtmlPreferer}.
*
* @param context
* the context from the parent
* @param next
* the [EMAIL PROTECTED] Restlet} to call after filtering.
*/
public HtmlPreferer(Context context, Restlet next) {
super(context, next);
}
/**
* Allows filtering before processing by the next Restlet.
*
* @param request
* The request to filter.
* @param response
* The response to update.
* @return The continuation status, see
* [EMAIL PROTECTED] Filter#beforeHandle(Request, Response)}
* @see Filter#beforeHandle(Request, Response)
*/
@Override
protected int beforeHandle(Request request, Response response) {
if (shouldChangeToPrefereHtml(request))
prefereHtml(request);
return super.beforeHandle(request, response);
}
/**
* Returns the quality of accepted media type application/xhtml+xml, or
* null, if not present in the given request.
*
* @param request
* @return the quality of accepted media type application/xhtml+xml, or
* null, if not present in the given request.
* @see #getHtmlMinQuality(Request)
*/
protected Float getAppXhtmlQuality(Request request) {
return getHtmlXmlMtQualities(request)[MT_PREF_APP_XHTML];
}
/**
* Returns the quality of accepted media type app/xml, or null, if not
* present in the given request.
*
* @param request
* @return the quality of accepted media type app/xml, or null, if not
* present in the given request.
* @see #getXmlMaxQuality(Request)
*/
protected Float getAppXmlQuality(Request request) {
return getHtmlXmlMtQualities(request)[MT_PREF_APP_XML];
}
/**
* Returns the lowest quality of the HTML types (text/html and
* application/xhtml+xml), or null, if not available.
*
* @param request
* @return the lowest quality of the HTML types (text/html and
* application/xhtml+xml), or null, if not available.
* @see #getTextHtmlQuality(Request)
* @see #getAppXhtmlQuality(Request)
*/
protected Float getHtmlMinQuality(Request request) {
Float xhtmlQuality = getAppXhtmlQuality(request);
Float htmlQuality = getTextHtmlQuality(request);
if (xhtmlQuality == null)
return htmlQuality;
if (htmlQuality == null)
return xhtmlQuality;
return Math.min(xhtmlQuality, htmlQuality);
}
@SuppressWarnings("unchecked")
private Float[] getHtmlXmlMtQualities(Request request) {
Float[] htmlXmlQualities;
Map<String, Object> attributes = request.getAttributes();
htmlXmlQualities = (Float[]) attributes.get(MT_QUALITY_ARRAY);
if (htmlXmlQualities == null) {
htmlXmlQualities = new Float[4];
List<Preference<MediaType>> acceptedMediaTypes = request
.getClientInfo().getAcceptedMediaTypes();
for (Preference<MediaType> accPref : acceptedMediaTypes) {
MediaType accMediaType = accPref.getMetadata();
if (accMediaType.equals(MediaType.TEXT_HTML, true))
htmlXmlQualities[MT_PREF_TEXT_HTML] = accPref.getQuality();
if (accMediaType.equals(MediaType.TEXT_XML, true))
htmlXmlQualities[MT_PREF_TEXT_XML] = accPref.getQuality();
if (accMediaType.equals(MediaType.APPLICATION_XHTML_XML, true))
htmlXmlQualities[MT_PREF_APP_XHTML] = accPref.getQuality();
if (accMediaType.equals(MediaType.APPLICATION_XML, true))
htmlXmlQualities[MT_PREF_APP_XML] = accPref.getQuality();
}
attributes.put(MT_QUALITY_ARRAY, htmlXmlQualities);
}
return htmlXmlQualities;
}
/**
* Returns the quality of accepted media type text/html, or null, if not
* present in the given request.
*
* @param request
* @return the quality of accepted media type text/html, or null, if not
* present in the given request.
* @see #getHtmlMinQuality(Request)
*/
protected Float getTextHtmlQuality(Request request) {
return getHtmlXmlMtQualities(request)[MT_PREF_TEXT_HTML];
}
/**
* Returns the quality of accepted media type text/xml, or null, if not
* present in the given request.
*
* @param request
* @return the quality of accepted media type text/xml, or null, if not
* present in the given request.
* @see #getXmlMaxQuality(Request)
*/
protected Float getTextXmlQuality(Request request) {
return getHtmlXmlMtQualities(request)[MT_PREF_TEXT_XML];
}
/**
* Returns the highest quality of the XML types (text/xml and
* application/xml), or null, if not available.
*
* @param request
* @return the highest quality of the XML types (text/xml and
* application/xml), or null, if not available.
* @see #getAppXmlQuality(Request)
* @see #getTextXmlQuality(Request)
*/
protected Float getXmlMaxQuality(Request request) {
Float appXmlQuality = getAppXmlQuality(request);
Float textXmlQuality = getTextXmlQuality(request);
if (appXmlQuality == null)
return textXmlQuality;
if (textXmlQuality == null)
return appXmlQuality;
return Math.max(appXmlQuality, textXmlQuality);
}
/**
* Alters the request, that HTML is prefered before XML.
*
* @param request
* the request to alter.
*/
protected void prefereHtml(Request request) {
Float xmlQualityO = getXmlMaxQuality(request);
if (xmlQualityO == null)
return;
float xmlQuality = xmlQualityO;
float htmlMinQuality;
if (xmlQuality < 1) {
htmlMinQuality = xmlQuality + 0.001f;
} else {
lowerWithQuality(request, xmlQuality);
htmlMinQuality = 1;
}
htmlPrefsMin(request, htmlMinQuality);
}
/**
* Lowers all accepted media type Preferences with the given quality to
* 0.001 (littlest accuracy for HTML qualities). Also lowers recursive the
* preferences with the goal quality.
*
* @param request
* @param quality
*/
private void lowerWithQuality(Request request, float quality) {
List<Preference<MediaType>> acceptedMediaTypes = request
.getClientInfo().getAcceptedMediaTypes();
float goalQuality = quality - 0.001f;
boolean alreadyAvailable = false;
for (Preference<MediaType> accPref : acceptedMediaTypes) {
if (accPref.getQuality() == goalQuality) {
alreadyAvailable = true;
break;
}
}
if (alreadyAvailable)
lowerWithQuality(request, goalQuality);
for (Preference<MediaType> accPref : acceptedMediaTypes) {
if (accPref.getQuality() == quality)
accPref.setQuality(goalQuality);
}
}
/**
* sets the quality of the preferences of the HTML media types (text/html
* and app/xhtml) at least to the given quality.
*
* @param request
* @param htmlQuality
*/
private void htmlPrefsMin(Request request, float htmlQuality) {
for (Preference<MediaType> accPref : request.getClientInfo()
.getAcceptedMediaTypes()) {
MediaType accMediaType = accPref.getMetadata();
if (accMediaType.equals(MediaType.APPLICATION_XHTML_XML, true)
|| accMediaType.equals(MediaType.TEXT_HTML, true)) {
if (accPref.getQuality() < htmlQuality)
accPref.setQuality(htmlQuality);
}
}
}
/**
* This method checks, if HTML should be prefered for the given request. The
* check may be overridden or complemented by override that method.
*
* @param request
* the request to check.
* @return true, if the [EMAIL PROTECTED] Request} should be altered, or
false if not.
*/
protected boolean shouldChangeToPrefereHtml(Request request) {
Float htmlQuality = getHtmlMinQuality(request);
Float xmlQuality = getXmlMaxQuality(request);
if (htmlQuality == null || xmlQuality == null)
return false;
return xmlQuality >= htmlQuality;
}
}/*
* Copyright 2005-2008 Noelios Consulting.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License"). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* http://www.opensource.org/licenses/cddl1.txt See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at http://www.opensource.org/licenses/cddl1.txt If
* applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*/
package org.restlet.test.jaxrs;
import java.util.List;
import org.restlet.Restlet;
import org.restlet.data.MediaType;
import org.restlet.data.Preference;
import org.restlet.data.Request;
import org.restlet.ext.jaxrs.HtmlPreferer;
import junit.framework.TestCase;
/**
* This TextCase checks the [EMAIL PROTECTED] HtmlPreferer}.
*
* @author Stephan Koops
*/
public class HtmlPrefererTest extends TestCase {
private static final HtmlPreferer HTML_PREFERER = new HtmlPreferer(null,
new Restlet());
/**
* @param accMediaTypes
* @param mediaType
* @param quality
*/
private void addMediaTypePref(List<Preference<MediaType>> accMediaTypes,
MediaType mediaType, float quality) {
accMediaTypes.add(new Preference<MediaType>(mediaType, quality));
}
/**
* @param accMediaTypes
* @param q0
* @param q1
* @param q2
* @param q3
* @param q4
*/
private void check(List<Preference<MediaType>> accMediaTypes, float q0,
float q1, float q2, float q3, float q4) {
assertEquals(5, accMediaTypes.size());
Preference<MediaType> amt0 = accMediaTypes.get(0);
Preference<MediaType> amt1 = accMediaTypes.get(1);
Preference<MediaType> amt2 = accMediaTypes.get(2);
Preference<MediaType> amt3 = accMediaTypes.get(3);
Preference<MediaType> amt4 = accMediaTypes.get(4);
assertEquals(q0, amt0.getQuality());
assertEquals(q1, amt1.getQuality());
assertEquals(q2, amt2.getQuality());
assertEquals(q3, amt3.getQuality());
assertEquals(q4, amt4.getQuality());
}
public void test1() {
Request request = new Request();
List<Preference<MediaType>> accMediaTypes = request.getClientInfo()
.getAcceptedMediaTypes();
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_ALL, 0.2f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_HTML, 1f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XHTML_XML, 0.9f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XML, 0.8f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_XML, 0.7f);
HTML_PREFERER.handle(request);
check(accMediaTypes, 0.2f, 1f, 0.9f, 0.8f, 0.7f);
}
public void test2() {
Request request = new Request();
List<Preference<MediaType>> accMediaTypes = request.getClientInfo()
.getAcceptedMediaTypes();
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_ALL, 0.2f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_HTML, 0.6f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XHTML_XML, 0.9f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XML, 0.8f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_XML, 0.7f);
HTML_PREFERER.handle(request);
check(accMediaTypes, 0.2f, 0.801f, 0.9f, 0.8f, 0.7f);
}
public void test3() {
Request request = new Request();
List<Preference<MediaType>> accMediaTypes = request.getClientInfo()
.getAcceptedMediaTypes();
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_ALL, 0.2f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_HTML, 0.6f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XHTML_XML, 1f);
addMediaTypePref(accMediaTypes, MediaType.IMAGE_BMP, 0.8f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_PLAIN, 0.7f);
HTML_PREFERER.handle(request);
check(accMediaTypes, 0.2f, 0.6f, 1f, 0.8f, 0.7f);
}
public void test4() {
Request request = new Request();
List<Preference<MediaType>> accMediaTypes = request.getClientInfo()
.getAcceptedMediaTypes();
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_ALL, 0.2f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_HTML, 0.6f);
addMediaTypePref(accMediaTypes, MediaType.APPLICATION_XML, 1f);
addMediaTypePref(accMediaTypes, MediaType.IMAGE_BMP, 0.999f);
addMediaTypePref(accMediaTypes, MediaType.TEXT_PLAIN, 0.7f);
HTML_PREFERER.handle(request);
check(accMediaTypes, 0.2f, 1f, 0.999f, 0.998f, 0.7f);
}
}