3 Mar 22:25
Re: HTML with REST
Stephan Koops <Stephan.Koops <at> web.de>
2008-03-03 21:25:06 GMT
2008-03-03 21:25:06 GMT
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:
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 =)
- ROn 2/18/08, Stephan Koops <Stephan.Koops <at> web.de> 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 {@link #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 {@link #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 {@link HtmlPreferer}. You should use constructor * {@link #HtmlPreferer(Context)} or {@link #HtmlPreferer(Context, Restlet)}. */ @Deprecated public HtmlPreferer() { super(); } /** * Creates a new {@link HtmlPreferer}. You can give also the next restlet * by using constructor {@link #HtmlPreferer(Context, Restlet)}. * * @param context * the context from the parent */ public HtmlPreferer(Context context) { super(context); } /** * Creates a new {@link HtmlPreferer}. * * @param context * the context from the parent * @param next * the {@link 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 * {@link 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 {@link 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 {@link 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); } }
RSS Feed