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"}); |
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) |
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 |
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.
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) |
//"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!
Thank you very much for the info ;)!
Thanks You. This help a lot.
Thank you! Very helpful.
Thank you for this. It saved me a lot of time.
I wonder if the tag modifies the request in a way like this.
This class is perfect, thank you and keep thinkin Java.
“jsp:param” standar tag
Excellent and bright work.
I used your solution to automated appending parameters to REST service. Using servlet filter.
Here is the sample code:
And here is the relevant web.xml
[…] Member Somehow I think I should have read this before… http://ocpsoft.com/opensource/how-to-safely-add-modify-servlet-request-parameter-values/ Posted 8 months ago […]
Thank you for your work, thank you for sharing, thank you to take the time to realize your idea building those projects !
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:
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):
I tested this on Tomcat 7.
I believe you are correct! Updated 🙂
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.
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.
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 ?
[…] 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 […]
[…] 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 […]