February 4th, 2010 by Lincoln Baxter III

Safely add / modify Servlet request parameter values

PrettyFaces

Sometimes things are worth writing about.

While working on the PrettyFaces: bookmarking, and SEO extensions for JSF / JSF2, I came across a need to modify the current request parameters in order to “trick” the system into thinking that additional query parameters had been supplied.

Naively, I tried:

request.getParameterMap().put("name", new String[]{"value1", "value2"});
But that doesn’t work, because you aren’t allowed to modify request parameter values once the request has begun processing:
java.lang.IllegalStateException: Cannot find message associated with key parameterMap.locked at org.apache.catalina.util.ParameterMap.put(ParameterMap.java:213)
This means we need to wrap the current request with our own special HttpServletRequestWrapper that will provide the ability to merge in some extra parameters up front.

The magic is quite simple:

But be careful! If you call request.getParameterMap() or any method that would call request.getReader() and begin reading, you will prevent any further calls to request.setCharacterEncoding(...), which is a big deal if you are processing i18n requests, or requests with a special content type. You need to ensure that the new parameters are merged with the original request parameters only when the parameters are requested, so that everyone still has a chance to set the encoding. Otherwise you get nasty warnings like this:
WARNING: PWC4011: Unable to set request character encoding to UTF-8 from context 
/ocpsoft-pretty-faces-tests, because request parameters have already been read, or 
ServletRequest.getReader() has already been called

Our little HttpServletRequestWrapper:

You could simply provide a modifiable map implementation, but that would break the contract of the request parameter map. So using a bit of lazy processing, here’s how we avoid reading the parameters until they are actually requested, while still maintaining the immutable nature of the Map.
Exhibit 0
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class PrettyFacesWrappedRequest extends HttpServletRequestWrapper
{
    private final Map<String, String[]> modifiableParameters;
    private Map<String, String[]> allParameters = null;

    /**
     * Create a new request wrapper that will merge additional parameters into
     * the request object without prematurely reading parameters from the
     * original request.
     * 
     * @param request
     * @param additionalParams
     */
    public PrettyFacesWrappedRequest(final HttpServletRequest request, 
                                                    final Map<String, String[]> additionalParams)
    {
        super(request);
        modifiableParameters = new TreeMap<String, String[]>();
        modifiableParameters.putAll(additionalParams);
    }

    @Override
    public String getParameter(final String name)
    {
        String[] strings = getParameterMap().get(name);
        if (strings != null)
        {
            return strings[0];
        }
        return super.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap()
    {
        if (allParameters == null)
        {
            allParameters = new TreeMap<String, String[]>();
            allParameters.putAll(super.getParameterMap());
            allParameters.putAll(modifiableParameters);
        }
        //Return an unmodifiable collection because we need to uphold the interface contract.
        return Collections.unmodifiableMap(allParameters);
    }

    @Override
    public Enumeration<String> getParameterNames()
    {
        return Collections.enumeration(getParameterMap().keySet());
    }

    @Override
    public String[] getParameterValues(final String name)
    {
        return getParameterMap().get(name);
    }
}
Once you have your handy request wrapper, you can get working! Keep in mind that you’ll need to do a servlet forward if you want your entire system to gain access to your new parameters. Otherwise, you can just pass this new wrapped object around as needed.
//"request" is the current HttpServletRequest
Map<String, String[]> extraParams = new TreeMap<String, String[]>()
HttpServletRequest wrappedRequest = new PrettyFacesWrappedRequest(request, extraParams);
request.getRequestDispatcher("url").forward(wrappedRequest, response)
Happy hacking! 😉 As a final note, I’d like to say that JEE 6 has really turned out to be a great experience. Very pleasant to program on. Well done, SUN!

Posted in OpenSource

16 Comments

  1. Thank you very much for the info ;)!

    1. Richi Varma says:

      Thanks You. This help a lot.

  2. Stefan says:

    Thank you! Very helpful.

  3. Ulrich says:

    Thank you for this. It saved me a lot of time.

  4. Marco says:

    I wonder if the tag modifies the request in a way like this.
    This class is perfect, thank you and keep thinkin Java.

  5. Marco says:

    “jsp:param” standar tag

  6. David says:

    Excellent and bright work.
    I used your solution to automated appending parameters to REST service. Using servlet filter.
    Here is the sample code:

    package com.demo.filters;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    
    public class AppendParametersFilter implements Filter {
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    		Map<String, String[]> additionalParams = new HashMap<String, String[]>();
    		additionalParams.put("sample Param 1", new String[] {"value 1.1", "value 1.2"});
    		additionalParams.put("sample Param 2", new String[] {"value 2"});
    		EnhancedHttpRequest enhancedHttpRequest = new EnhancedHttpRequest((HttpServletRequest) request, additionalParams);
    
    		// pass the request along the filter chain
    		chain.doFilter(enhancedHttpRequest, response);
    	}
    
    	@Override
    	public void destroy() {	}
    	@Override
    	public void init(FilterConfig filterConfig) throws ServletException { }
    }

    And here is the relevant web.xml

    <filter>
    		<description>Append a URL parameter on the request</description>
    		<display-name>AppendParametersFilter</display-name>
    		<filter-name>AppendParametersFilter</filter-name>
    		<filter-class>com.demo.filters.AppendParametersFilter</filter-class>
    	</filter>
    	<filter-mapping>
    		<filter-name>AppendParametersFilter</filter-name>
    		<url-pattern>/pl</url-pattern>
    		<url-pattern>/ParametersListing</url-pattern>
    	</filter-mapping>
    	<servlet>
    		<description>
    		</description>
    		<display-name>ParametersListing</display-name>
    		<servlet-name>ParametersListing</servlet-name>
    		<servlet-class>com.demo.servlets.ParametersListing</servlet-class>
    		<load-on-startup>2</load-on-startup>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>ParametersListing</servlet-name>
    		<url-pattern>/ParametersListing</url-pattern>
    		<url-pattern>/pl</url-pattern>
    	</servlet-mapping>
  7. Guillaume says:

    Thank you for your work, thank you for sharing, thank you to take the time to realize your idea building those projects !

  8. Aleksandar Dovnikovic says:

    Thank you for posting this as it was helpful, however I think I may have found one small bug. The problem is with jsp tag jsp:forward when a parameter is forwarded. For example:

    <jsp:forward page="receive.jsp">
    <jsp:param name="test" value="123"/>
    </jsp:forward>

    It seems the parameter is added to the request after the filter is finished, so in order to preserve this parameter, the last line in your getParameter method should actually be (instead of return null):

    return super.getParameter(name);

    I tested this on Tomcat 7.

  9. I believe you are correct! Updated 🙂

    1. Hm. I tested this on JBoss AS7 and it works, but it also works with your patch so I’ve added a test-case to http://ocpsoft.org/rewrite/ including your fix; hopefully this won’t happen in the future.

  10. Actually the reason this works in rewrite is because we merge parameter maps when the new request is created on forwards. Test added. Your solution works as well.

  11. Rohit says:

    Thanks for the sample code, very helpful and I believe this will work in the case what I am trying to achieve but a small issue

    I added HandlerInterceptor to the url in spring and got preHandle and postHandle overridden but when I try to redirect from there it doesn’t work. Any comments ?

  12. […] I have a web application designed to play the role of a search engine. Its backend is developed in Java and its frontend in angularJS. I have the following class named PrettyFacesWrappedRequest that i found on the following link with some explanations : link here […]

  13. […] original parameters (only by using HttpServletRequestWrapper). Any ideas?.. Update: it seems that http://ocpsoft.org/opensource/how-to-safely-add-modify-servlet-request-parameter-values/ addresses this […]

Reply to Rohit




Please note: In order to submit code or special characters, wrap it in

[code lang="xml"][/code]
(for your language) - or your tags will be eaten.

Please note: Comment moderation is enabled and may delay your comment from appearing. There is no need to resubmit your comment.