I've created two sets of servlets: Views and Controllers/Handlers
Notification here is a status message for the user. Example: "You have successfully updated blah..."
If I use requestDispatcher.forward()
in controllers and the user refreshes (after the Controller has passed control to view/jsp) the page by confirming resend there is a chance of duplicate actions being performed
If I use response.sendRedirect()
I cannot seem to send any notifications without setting these in the session
Is there a good design practice that helps here? Any good link to MVC for java w/o frameworks that handles this particular scenario would be appreciated.
I am not using Spring or Struts - just plain old HTTPServlets
Example - Controller:
public XServlet extends HttpServlet{
public void processRequest(request, response) throws ...{
//Do some stuff here
if(success){
setUserMessage("Hooray ur profile pic is uploaded!");
} else {
setUserMessage("Oh no! We couldn't upload that file its too biG!");
}
//Send the notification
request.setAttribute("status", getUserMessage());
request.getRequestDispatcher("editProfile.jsp").forward(request, response);
}
}
Doing this means that if the user tries to refresh the page the control will again pass to this controller and some actions may be repeated unnecessarily.
However if I use sendRedirect()
then I cannot show the status message without either resorting to a session attribute or appending it to the url.
You're looking for the "flash scope".
The flash scope is backed by a short living cookie which is associated with a data entry in the session scope. Before the redirect, a cookie will be set on the HTTP response with a value which is uniquely associated with the data entry in the session scope. After the redirect, the presence of the flash scope cookie will be checked and the data entry associated with the cookie will be removed from the session scope and be put in the request scope of the redirected request. Finally the cookie will be removed from the HTTP response. This way the redirected request has access to request scoped data which was been prepared in the initial request.
In plain Servlet terms that's thus like below:
Create the flash scope and add entries:
String message = "Some message";
// ...
Map<String, Object> flashScope = new HashMap<>();
flashScope.put("message", message);
Before redirect, store it in the session keyed by an unique ID and set it as cookie:
String flashScopeId = UUID.randomUUID().toString();
request.getSession().setAttribute(flashScopeId, flashScope);
Cookie cookie = new Cookie("flash", flashScopeId);
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
// ...
response.sendRedirect(request.getContextPath() + "/someservlet");
In next request, find the flash cookie, map it back into request scope and delete cookie:
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("flash".equals(cookie.getName())) {
Map<String, Object> flashScope = (Map<String, Object>) request.getSession().getAttribute(cookie.getValue());
if (flashScope != null) {
request.getSession().removeAttribute(cookie.getValue());
for (Entry<String, Object> entry : flashScope.entrySet()) {
request.setAttribute(entry.getKey(), entry.getValue());
}
}
cookie.setValue(null);
cookie.setMaxAge(0);
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
}
}
}
This could be further abstracted using context-specific helper method like setFlashAttribute()
and a servlet filter with a response wrapper.