June 14, 2012

Spring Not Setting Cookie On AJAX Response

Overview

I was working on a problem where any request to a Spring Controller needed to have a Cookie set on its response.  Normal page to page navigation worked fine, but I noticed some (not all) of the of AJAX responses were missing the Cookie.  Even though I verified through log files and stepping through the code that the Cookie was being set on the Response, once the AJAX Response got back to the browser, there was no Cookie.  This article describes how I was able to configure Spring to always return a Cookie on a Spring Controller AJAX Response.

CookieInterceptor

A class was created in the project  which was responsible for setting the cookie on the Response.  Call  this class CookieInterceptor and have it implement the org.springframework.web.servlet.HandlerInterceptor interface.  A Spring Intecepter is similar to an Java EE Filter in that it allows the request and response to be intercepted and cross cutting concerns be added in the Interceptor instead of adding them to every Controller.  In my case, the cross cutting concern was to make sure the Response always had an updated value set for a Cookie.  I implemented HandlerInterceptor interface and added the code to set the Cookie in the postHandle() method.

Registering the Interceptor with Spring is easy:

<mvc:interceptors>
   <bean id="cookieInterceptor" class="com.widgit.CookieInterceptor"/>
</mvc:interceptors>

Testing

After creating CookieInterceptor and registering it with Spring, I performed some testing.  Navigating from page to page the Cookie value was found in the Response and the browser was getting the new Cookie as it should.

...however...

When testing AJAX calls, I got mixed results.  If  the Controller used the @ResponseBody  annotation to return the AJAX Response, I saw no Cookie in the Response.  However, if the Controller did NOT use @ResponseBody but instead returned a view string which was turned into a JSON object later, I did get the Cookie.  In all cases, I verified through log files and stepping through the code that the code for the CookieInterceptor was being called and the cookie was being set.  So why was one case getting the Cookie, and the other case not getting the Cookie?

Content-Type

A difference I saw in the responses had to do with the Response Content-Type.  When using the @ResponsBody annotation, the Response had a Content-Type="application/json".  However using other methods to return  the responses, the Response had a Content-type="text/html".  When the Content-Type was application/json no Cookie was set however when the Content-Type was text/html, the Cookie was there.  But why was this?

A Matter of Timing

At first I thought perhaps a Cookie could not be set on a Response of type application/json.  Research showed this was not the case.  So instead I turned to the CookieInterceptor.  The implementation had the Cookie setting code in the postHandle() method and even though this code was always being run, The Cookie was not being set when the Controller used @ResponseBody.  Through some research, my colleague Joseph Flatt thought this might be a matter of timing, meaning that for some reason when @ResponsBody was used to return the Response by the time you got back to running the code in the CookieInterceptor it was too late to make any changes to the Response: Perhaps the response had already been written.  So instead of using the postHandle() method, we tested using the preHandle() method.  Moving the Cookie setting code to the preHandle() method worked!  The Cookie was properly set on all Controller requests regardless of the request being page to page navigation or AJAX calls.

Conclusion

If you want Spring to set a Cookie on ALL requests to Controllers - whether they be page to page requests or AJAX calls, put your code in the preHandle() method of the  HandlerInterceptor interface.




2 comments:

  1. hey Michael

    Thanks for the information.. Can you please share the example code on how you were setting the cookie using prehandle() method? As I have searched a lot on internet and couldn't find an appropriate solution.. Yours is the closest to what I need to do for my app... thanks! :)

    ReplyDelete
  2. Thank you so much, it worked :)

    ReplyDelete