views:

36049

answers:

16

Im using $.post() to call a Servlet using Ajax and then use the resulting HTML fragment to replace a div element in the User's current page. However, if the session timeouts the server sends a redirect directive to send the user to the login page. Nonetheless, JQuery is replacing the div element with the contents of the login page, forcing the user's eyes to witness a rare scene indeed.

How can I manage a redirect directive from an Ajax call?

  • jQuery 1.2.6
+3  A: 

You can handle the AJAX response code to see whether it is a "200" or not (redirect is a 3xx) code

Guido
I'm using the "digested" post method, so I'm not dealing with response codes.
Elliot Vargas
hummm... I think dealing with response codes is the only way to handle your problem. Does jquery provides an alternative method you could use to obtain the response code ?
Guido
+4  A: 

Use the low-level $.ajax() call:

$.ajax({
  url: "/yourservlet",
  data: { },
  complete: function(xmlHttp) {
    // xmlHttp is a XMLHttpRquest object
    alert(xmlHttp.status);
  }
});

Try this for a redirect:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
Till
I was under the impression that the initial redirect will have the code 300 and then the page that finally loads (i.e. the login page) will have a code of 200
Sugendran
I was trying to avoid the low level stuff. Anyway, suppose I use something like what you describe, how do I force a browser redirection once I detect that the HTTP code is 3xx? My goal is to redirect the user, not just to announce that his/her session has expired.
Elliot Vargas
Btw, $.ajax() is not very, very low-level. It's just low-level in terms of jQuery because there is $.get, $.post, etc. which are much more simple than $.ajax and all its options.
Till
I also extended my answer. Let me know if this helps!
Till
Yeah, I now using JQuery is not really low-level, I was being dramatic! BTW, your answer was really helpful. Thanks!
Elliot Vargas
Oh boy! Sorry I had to "un-accept" your answer, it still very helpful. Thing is, redirections are automatically managed by the XMLHttpRequest, hence I ALWAYS get a 200 status code after the redirection (sigh!). I think I will have to do something nasty like parsing the HTML and look for a marker.
Elliot Vargas
Another thing, it's xmlHttp.status and not xmlHttp.code
Elliot Vargas
I corrected the code. Thanks. Sorry that I couldn't be of more help. By the way, this is one of the reasons why people implement a response format (e.g. XML or JSON) which contains a more verbose status etc.. Look into that maybe. Or let me know, I can post an example if you need.
Till
Just curious, if the session ends at the server, doesn't that mean the server sends a different SESSIONID? couldn't we just detect that?
Salamander2007
NOTE: This does not work for redirects. ajax will go to the new page and return its status code.
acidzombie24
A: 

Additionally you will probably want to redirect user to the given in headers URL. So finally it will looks like this:

$.ajax({
    //.... other definition
    complete:function(xmlHttp){
        if(xmlHttp.status.toString()[0]=='3'){
        top.location.href = xmlHttp.getResponseHeader('Location');
    }
});

UPD: Opps. Have the same task, but it not works. Doing this stuff. I'll show you solution when I'll find it.

Vladimir Prudnikov
A: 

in the servlet you should put response.setStatus(response.SC_MOVED_PERMANENTLY); to send the '301' xmlHttp status you need for a redirection...

and in the $.ajax function you should not use the .toString() function..., just

if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }

the problem is it is not very flexible, you can't decide where you want to redirect..

redirecting through the servlets should be the best way. but i still can not find the right way to do it.

+9  A: 

No browsers handles 301 and 302 responses correctly. And in fact the standard even says they should handle them "transparently" which is a MASSIVE headache for Ajax Library vendors. In Ra-Ajax we were forced into using HTTP response status code 278 (just some "unused" success code) to handle transparently redirects from the server...

This really annoys me, and if someone here have some "pull" in W3C I would appreciate that you could let W3C know that we really need to handle 301 and 302 codes ourselves...! ;)

Thomas Hansen
+5  A: 

The solution that was eventually implemented was to use a wrapper for the callback function of the Ajax call and in this wrapper check for the existence of a specific element on the returned HTML chunk. If the element was found then the wrapper executed a redirection. If not, the wrapper forwarded the call to the actual callback function.

For example, our wrapper function was something like:


    function cbWrapper(data, funct){
     if($("#myForm", data).size() > 0)
      top.location.href="login.htm";//redirection
     else
      funct(data);
    }
    

Then, when making the Ajax call we used something like:


    $.post("myAjaxHandler", 
        {
      param1: foo,
      param2: bar
        },
        function(data){
         cbWrapper(data, myActualCB);
        }, 
        "html");
    

This worked for us because all Ajax calls always returned HTML inside a DIV element that we use to replace a piece of the page. Also, we only needed to redirect to the login page.

Elliot Vargas
+7  A: 

I solved this issue by:

  1. Adding a custom header to the Response

    public ActionResult Index(){
      if (!HttpContext.User.Identity.IsAuthenticated)
      {
        HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
      }
      return View()    
    }
    
  2. Bind a Javascript function to the ajaxSuccess event and check to see if the header exists

    $('body').bind('ajaxSuccess',function(event,request,settings){
        if (request.getResponseHeader('REQUIRES_AUTH') === '1'){
           window.location = '/';
        };
    });
    
SuperG
What an awesome solution. I like the idea of a one-stop solution. I need to check for a 403 status, but I can use the ajaxSuccess bind on body for this (which is what I was really looking for.) Thanks.
Bretticus
I just did this and found that I needed ajaxComplete where I was using the $.get() function and any status other than 200 was not firing. In fact, I could have probably just bound to ajaxError instead. See my answer below for more details.
Bretticus
A: 

Based on my brief testing of Firefox, Safari, Opera, IE6/7, it seems the XMLHttpRequest.status does not return the same values and its not compatible across different browsers. I haven't found a more elegant solution.

cheeming
A: 

In Firefox I don't always receive a 302 from jQuery; in fact, most of the time I receive an xhr.status of 0.

Brad Gessler
A: 

Putting together what Vladimir Prudnikov and Thomas Hansen said:

  • Change your server-side code to detect if it's an XHR. If it is, set the response code of the redirect to 278. In django:
   if request.is_ajax():
      response.status_code = 278

This makes the browser treat the response as a success, and hand it to your Javascript.

  • In your JS, make sure the form submission is via Ajax, check the response code and redirect if needed:
$('#my-form').submit(function(event){ 

  event.preventDefault();   
  var options = {
    url: $(this).attr('action'),
    type: 'POST',
    complete: function(response, textStatus) {    
      if (response.status == 278) { 
        window.location = response.getResponseHeader('Location')
      }
      else { ... your code here ... } 
    },
    data: $(this).serialize(),   
  };   
  $.ajax(options); 
});
Graham King
+29  A: 

I read this question and implemented the approach that has been stated regarding setting the response status code to 278 in order to avoid the browser transparently handling the redirects. Even though this worked, I was a little dissatisfied as it is a bit of a hack.

After more digging around, I ditched this approach and used JSON. In this case, all responses to ajax requests have the status code 200 and the body of the response contains a JSON object that is constructed on the server. The javascript on the client can then use the JSON object to decide what it needs to do.

I had a similar problem to yours. I perform an ajax request that has 2 possible responses: one that redirects the browser to a new page and one that replaces an existing HTML form on the current page with a new one. The jquery code to do this looks something like:

$.ajax({
    type: "POST",
    url: reqUrl,
    data: reqBody,
    dataType: "json",
    success: function(data, textStatus) {
        if (data.redirect) {
            // data.redirect contains the string URL to redirect to
            window.location.href = data.redirect;
        }
        else {
            // data.form contains the HTML for the replacement form
            $("#myform").replaceWith(data.form);
        }
    }
});

The JSON object "data" is constructed on the server to have 2 members: data.redirect and data.form. I found this approach to be much better.

Steg
Cool, nice solution !
Elliot Vargas
If it works for you, you should mark your question solved.
stefan
A: 

in any of the above mentioned cases it doesn't work in firefox. Ajax can not redirect a page but it can only update the content. So window.location.href doesn't work in Ajax onSuccess methd.

sridhar
A: 

I just wanted to latch on to any ajax requests for the entire page. @SuperG got me started. Here is what I ended up with:

// redirect ajax requests that are redirected, not found (404), or forbidden (403.)
$('body').bind('ajaxComplete', function(event,request,settings){
        switch(request.status) {
            case 301: case 404: case 403:                    
                window.location.replace("http://mysite.tld/login");
                break;
        }
});

I wanted to specifically check for certain http status codes to base my decision on. However, you can just bind to ajaxError to get anything other than success (200 only perhaps?) I could have just written:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
Bretticus
+1  A: 

I have a simple solution that works for me, no server code change needed...just add a tsp of nutmeg...

$(document).ready(function ()
{
    $(document).ajaxSend(
    function(event,request,settings)
    {
        var intercepted_success = settings.success;
        settings.success = function( a, b, c ) 
        {  
            if( request.responseText.indexOf( "<html>" ) > -1 )
                window.location = window.location;
            else
                intercepted_success( a, b, c );
        };
    });
});

I check the presence of html tag, but you can change the indexOf to search for whatever unique string exists in your login page...

Timmerz
A: 

There is a problem with version 1.4.2 in jQuery. to solve this, add async:false in your ajax call like this:

$.ajax({
    type: "POST",
    url: reqUrl,
    data: reqBody,
    async: false,
    success: do_success
});
Nicolae Surdu
+1  A: 

Try

    $(document).ready(function () {
        if ($("#site").length > 0) {
            window.location = "<%= Url.Content("~") %>" + "Login/LogOn";
        }
    });

Put it on the login page. If it was loaded in a div on the main page, it will redirect til the login page. "#site" is a id of a div which is located on all pages except login page.

podeig