Ordering of values in HttpServletRequest.getParame

2019-01-11 11:03发布

问题:

HttpServletRequest.getParameterValues() returns a String[] containing all values of a given HTTP request parameter. Does anyone know if the order of the values in this array is guaranteed by specification to by the same as the order which those values were passed through in the request?

For example, if I have the GET query string x=1&x=2&x=3, am I guaranteed to receive the String[] {"1", "2", "3"} when I call getParameterValues()? It seems to work in practice, but I can't find anything which specifies that this must be the case, so I'm reluctant to rely on it.

回答1:

The javadoc for ServletRequest (v2.5 javadoc) does not mention anything about the ordering of values for that method. As such, I wouldn't rely on the order being preserved.


Update: also checked the spec document for 2.5, contains the following information relating to getParameterValues(). It does not mention anything about ordering with respect to the query string, so I think the behaviour you are seeing is implementation detail, not defined as part of the interface.

The parameters are stored as a set of name-value pairs. Multiple parameter values can exist for any given parameter name. The following methods of the ServletRequest interface are available to access parameters:

  • getParameter
  • getParameterNames
  • getParameterValues
  • getParameterMap

The getParameterValues method returns an array of String objects containing all the parameter values associated with a parameter name. The value returned from the getParameter method must be the first value in the array of String objects returned by getParameterValues. The getParameterMap method returns a java.util.Map of the parameter of the request, which contains names as keys and parameter values as map values.

For future reference, the Java Servlet specs can be downloaded from Sun, I mean Oracle's website. You can double check the specific servlet version you're interested in there.



回答2:

Yes, the order of values returned by getParamterValues(String) and entries in getParameterMap() is guaranteed by the Servlet Specification. Here's the passage:

Data from the query string and the post body are aggregated into the request parameter set. Query string data is presented before post body data. For example, if a request is made with a query string of a=hello and a post body of a=goodbye&a=world, the resulting parameter set would be ordered a=(hello, goodbye, world).

(This is from the "HTTP Protocol Parameters" section within "The Request" chapter of the Servlet Specifications (SRV.4.1 in version 2.4, SRV.3.1 in version 2.5).)

There doesn't appear to be a clean way to get the names in order (getParameterNames() does not return names in the order that the browser gave). I could, I suppose, parse the raw GET string from getQueryString() or parse the raw POST string from getInputStream(), but instead I think I will add another hidden parameter and then use getParameterValues(String) to get its order.

If you're curious why I want to the parameters in order, it's because I have controls that the user can rearrange using jQuery, and I want to know the order that the user has chosen:

<form>
  <ul id=variables>
    <li>
      <input name=hello>hello
      <input type=hidden name=variableOrder value=hello>
    <li>
      <input name=world>world
      <input type=hidden name=variableOrder value=world>
  </ul>
</form>
<script src="jquery.js"></script>
<script src="jquery-ui.js"></script>
<script>
  jQuery('#variables').sortable().disableSelection();
</script>


回答3:

It's indeed not explicitly definied in the Servlet spec, but at least the HTML forms spec definies it explicitly in the application/x-www-form-urlencoded section:

2.The control names/values are listed in the order they appear in the document.

So, that part is safe. Now the servletcontainer, most logically a decent and efficient implementation would process the HTTP input stream immediately as it comes in, so the parameters would be processed in the order as they appear in the request URI (GET) or request body (POST). Collecting them in a String[] is the most straightforward choice as it is also used as-is in the Servlet API, so I really don't see any reason to collect it in a HashSet like structure first, or do a Collections#shuffle() or whatever and then convert it to String[] afterwards.

I can at least tell from experience, Tomcat does it the right way, so all major containers/appservers which are built on top of Tomcat/Catalina (IBM Websphere, JBoss AS, Sun Glassfish, etc) will also behave so. I only don't have hands on experience with Weblogic, but I would be surprised if it processes it differently (read: less efficiently).

Only the ordering of the parameter names is not guaranteed, logically because it's backed by a HashMap.


Summarized: the parameters are collected in a HashMap<String, String[]>. The names are quaranteed not ordered due to the nature of the HashMap. The values (one parameter name can have multiple values, e.g. foo=bar1&foo=bar2&foo=bar3) are in turn however ordered due to the nature of String[], although this is not explicitly specified in the Servlet API.

To be on the safe side, you'd like to use a different approach, e.g.

foos=3&foo[0]=bar1&foo[1]=bar2&foo[2]=bar3

with

int foos = Integer.valueOf(request.getParameter("foos"));
for (int i = 0; i < foos; i++) {
    int foo = Integer.valueOf(request.getParameter("foo[" + i + "]"));
}


回答4:

I've got a problem to extract param-value map from HttpServletRequest in order in accordance with elements on the JSP. I wrote an OrderedRequestMap that parsers an application/x-www-form-urlencoded POST request body.

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;


public class OrderedRequestMap extends LinkedHashMap<String,String[]> {

private final String encoding;

public OrderedRequestMap(InputStream httpBody, String encoding) throws IOException {
    this.encoding = encoding;
    fill(httpBody);
}

private void fill(InputStream is) throws IOException {
    final InputStreamReader reader = new InputStreamReader(is, "ASCII");
    int c;
    StringBuilder sb = new StringBuilder(1000);
    while ((c = reader.read()) != -1) {
        char ch = (char)c;
        if (ch == '&') {
            put(sb.toString());
            sb = new StringBuilder(1000);
        } else {
            sb.append(ch);
        }
    }
    put(sb.toString());
}

private void put(String parameter) throws UnsupportedEncodingException {
    String[] pair = parameter.split("=");
    if (pair.length == 0 )
        return;
    String key = URLDecoder.decode(pair[0], encoding);
    String val = EMPTY_STR;
    if (pair.length > 1)
        val = URLDecoder.decode(pair[1], encoding);
    String[] values = get(key);
    if (values == null)
        values = new String[]{val};
    else {
        values = Arrays.copyOf(values, values.length+1);
        values[values.length - 1] = val;
    }
    put(key, values);
}


private static final String EMPTY_STR = "";
}

And call it like this

new OrderedRequestMap(request.getInputStream(), request.getCharacterEncoding());

Hope, it helps.



回答5:

It depends on HttpServletRequest interface implementation.