Spring Framework
  1. Spring Framework
  2. SPR-9655

HttpHeaders.getAccept() does not return all accept headers for Iplanet WebServer 7

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Complete
    • Affects Version/s: 3.0.7
    • Fix Version/s: 3.1.3, 3.2 RC1
    • Component/s: Web
    • Labels:
      None
    • Last commented by a User:
      true

      Description

      The HttpHeaders.getAccept() method is supposed to return a list of "accept" header MediaTypes that were on the servlet request. I noticed that only the first accept header was being returned when invoking this method. I debugged the code and found that getAccept() method invokes the getFirst() method and then splits the comma delimited string to get the list of MediaTypes. This works fine on tomcat because tomcat returns the request accept headers as a single comma separated string. However Oracle Iplanet Webserver returns each request accept header as a separate header entry so it will only ever return the first accept header.

      I ran into this problem when I was invoking a controller that returned a json object. If I invoked the controller directly from the browser url I would get the string representation of the json object when running in a tomcat server. However when I ran the same code in a Iplanet WebServer I would get "org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation". I started digging into the code and that is where I found that the AnnotationMethodHandlerAdapter.writeWithMessageConverters() method is calling the inputMessage.getHeaders().getAccept() method which when run inside a tomcat server would return 4 accept headers, one of which was "/". When ran in Iplanet WebServer it only ever returned "text/html". This would cause the error since the MappingJacksonHttpMessageConverter only accepts "application/json".

      Basically the HttpHeaders.getAccept() method needs to take into account that not all servlet containers return all the header values as a single comma separated string. You should be able to just check the length of the returned list of strings when getting the accept headers and if the length is 1 then parse it. It if has more than one entry then just concatenate the string list together as a comma separated string.

        Activity

        Hide
        Marty Jones added a comment -

        Has there been any progress on this issue? Would it help if I created a source patch that would fix this issue?

        Show
        Marty Jones added a comment - Has there been any progress on this issue? Would it help if I created a source patch that would fix this issue?
        Hide
        Rossen Stoyanchev added a comment -

        What would help is to provide examples of the Accept header on the wire and how it's returned by the HttpServletRequest. Is this behavior specific to iPlanet WebServer version 7?

        Show
        Rossen Stoyanchev added a comment - What would help is to provide examples of the Accept header on the wire and how it's returned by the HttpServletRequest. Is this behavior specific to iPlanet WebServer version 7?
        Hide
        Rossen Stoyanchev added a comment -

        Re-classifying from Bug to Improvement.

        Show
        Rossen Stoyanchev added a comment - Re-classifying from Bug to Improvement.
        Hide
        Marty Jones added a comment -

        Rossen,

        I disagree that this should be classified as an improvement. The servlet spec "http://docs.oracle.com/javaee/5/api/javax/servlet/http/HttpServletRequest.html#getHeaders%28java.lang.String%29" states that "Some headers, such as Accept-Language can be sent by clients as several headers each with a different value rather than sending the header as a comma separated list.".

        That is what is happening in this case. Tomcat returns a single accept header as a comma separated string. Iplanet returns a separate accept header for each accept value passed over. I debugged the ServletServerHttpRequest.getHeaders() method and captured the results from line 95 of this class.

        Here are the results:

        tomcat -> The enumeration returns one value of "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8".

        iplanet -> The enumeration returns 4 values ["text/html","application/xhtml+xml","application/xml;q=0.9","*/*;q=0.8"]

        This is causing us major issues because the HttpHeaders.getAccept() is only ever returning the "text/html" entry on iPlanet.

        Show
        Marty Jones added a comment - Rossen, I disagree that this should be classified as an improvement. The servlet spec "http://docs.oracle.com/javaee/5/api/javax/servlet/http/HttpServletRequest.html#getHeaders%28java.lang.String%29" states that "Some headers, such as Accept-Language can be sent by clients as several headers each with a different value rather than sending the header as a comma separated list.". That is what is happening in this case. Tomcat returns a single accept header as a comma separated string. Iplanet returns a separate accept header for each accept value passed over. I debugged the ServletServerHttpRequest.getHeaders() method and captured the results from line 95 of this class. Here are the results: tomcat -> The enumeration returns one value of "text/html,application/xhtml+xml,application/xml;q=0.9, / ;q=0.8". iplanet -> The enumeration returns 4 values ["text/html","application/xhtml+xml","application/xml;q=0.9","*/*;q=0.8"] This is causing us major issues because the HttpHeaders.getAccept() is only ever returning the "text/html" entry on iPlanet.
        Hide
        Rossen Stoyanchev added a comment - - edited

        I am not sure that interpretation is accurate. Here is a quote from the Servlet specification that is a little more clear than the Javadoc:

        The getHeader method returns a header given the name of the header.
        There can be multiple headers with the same name, e.g. Cache-Control
        headers, in an HTTP request. If there are multiple headers with the
        same name, the getHeader method returns the first header in the
        request. The getHeaders method allows access to all the header values
        associated with a particular header name, returning an Enumeration
        of String objects.
        

        The key cases are multiple headers with the same name or one header with comma-separated values. Even in the Javadoc it is phrased as an either-or proprosition: "several headers each with a different value rather than sending the header as a comma separated list."

        This is why I asked for what you see on the wire (with Firebug, Chrome dev tools, etc). I suspect it is a single header with a comma-separated list of media types that iPlanet is parsing. If that is the case we will consider it an improvement, or something extra we do to make sure it works on iPlanet.

        Show
        Rossen Stoyanchev added a comment - - edited I am not sure that interpretation is accurate. Here is a quote from the Servlet specification that is a little more clear than the Javadoc: The getHeader method returns a header given the name of the header. There can be multiple headers with the same name, e.g. Cache-Control headers, in an HTTP request. If there are multiple headers with the same name, the getHeader method returns the first header in the request. The getHeaders method allows access to all the header values associated with a particular header name, returning an Enumeration of String objects. The key cases are multiple headers with the same name or one header with comma-separated values. Even in the Javadoc it is phrased as an either-or proprosition: "several headers each with a different value rather than sending the header as a comma separated list." This is why I asked for what you see on the wire (with Firebug, Chrome dev tools, etc). I suspect it is a single header with a comma-separated list of media types that iPlanet is parsing. If that is the case we will consider it an improvement, or something extra we do to make sure it works on iPlanet.
        Hide
        Marty Jones added a comment -

        Sorry, I misunderstood what you were asking for. I used firebug to look at the headers and sure enough there is only use accept header.

        The value is "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8".

        Looks like Iplanet is parsing the header into multiple entries on our behalf (why, oh why do they do that?). The funny thing is that I believe Iplanet is running tomcat under the covers.

        Just another reason why I want to move away from Iplanet.

        Do you know of any work around to be able to get all the header values?

        Show
        Marty Jones added a comment - Sorry, I misunderstood what you were asking for. I used firebug to look at the headers and sure enough there is only use accept header. The value is "text/html,application/xhtml+xml,application/xml;q=0.9, / ;q=0.8". Looks like Iplanet is parsing the header into multiple entries on our behalf (why, oh why do they do that?). The funny thing is that I believe Iplanet is running tomcat under the covers. Just another reason why I want to move away from Iplanet. Do you know of any work around to be able to get all the header values?
        Hide
        Rossen Stoyanchev added a comment -

        Ok, good to know.

        Well, you could create a Filter that wraps the request and returns the Accept header value as a comma-separated String. As long as that doesn't impact any other code that relies on the iPlanet behavior, that should work. Another other option is to patch HttpHeaders temporarily (until this issue is resolved), for example by copying it to your source directory, preserving the package name, and modify it.

        Show
        Rossen Stoyanchev added a comment - Ok, good to know. Well, you could create a Filter that wraps the request and returns the Accept header value as a comma-separated String. As long as that doesn't impact any other code that relies on the iPlanet behavior, that should work. Another other option is to patch HttpHeaders temporarily (until this issue is resolved), for example by copying it to your source directory, preserving the package name, and modify it.
        Hide
        Marty Jones added a comment - - edited

        Here is a patch I made that resolves the issue:

        public List<MediaType> getAccept() {
        
            String value = null;
            List<String> headerValues = headers.get(ACCEPT);
        
            if (headerValues != null && headerValues.size() > 0) {
                if (headerValues.size() == 1) {
                    value = headerValues.get(0);
                }
                else if (headerValues.size() > 1) {
                    value = StringUtils.collectionToCommaDelimitedString(headerValues);
                }
            }
        
            return (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList());
        }
        
        Show
        Marty Jones added a comment - - edited Here is a patch I made that resolves the issue: public List<MediaType> getAccept() { String value = null ; List< String > headerValues = headers.get(ACCEPT); if (headerValues != null && headerValues.size() > 0) { if (headerValues.size() == 1) { value = headerValues.get(0); } else if (headerValues.size() > 1) { value = StringUtils.collectionToCommaDelimitedString(headerValues); } } return (value != null ? MediaType.parseMediaTypes(value) : Collections.<MediaType>emptyList()); }

          People

          • Assignee:
            Rossen Stoyanchev
            Reporter:
            Marty Jones
            Last updater:
            Chris Beams
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              1 year, 31 weeks, 1 day ago