jQuery Ajax 调用后如何管理重定向请求

我使用$.post()使用 Ajax 调用 servlet,然后使用生成的 HTML 片段替换用户当前页面中的div元素。但是,如果会话超时,服务器将发送重定向指令以将用户发送到登录页面。在这种情况下,jQuery 用登录页面的内容替换了div元素,迫使用户的眼睛确实看到了一个罕见的场景。

如何使用 jQuery 1.2.6 从 Ajax 调用管理重定向指令?

答案

我阅读了此问题,并实现了关于将响应HTTP 状态代码设置为 278 的方法,以避免浏览器透明地处理重定向。即使这行得通,我还是有点不满意,因为它有点破烂。

在深入研究之后,我放弃了这种方法并使用了JSON 。在这种情况下,对 AJAX 请求的所有响应都具有状态码 200,并且响应的主体包含在服务器上构造的 JSON 对象。然后,客户端上的 JavaScript 可以使用 JSON 对象来决定它需要做什么。

我有一个与您类似的问题。我执行一个 AJAX 请求,该请求有 2 种可能的响应:一种浏览器重定向到新页面,另一种新页面替换当前页面上的现有 HTML 表单。执行此操作的 jQuery 代码如下所示:

$.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);
        }
    }
});

JSON 对象 “data” 在服务器上构造为具有 2 个成员: data.redirectdata.form 。我发现这种方法要好得多。

我通过以下方法解决了这个问题:

  1. 向响应中添加自定义标头:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. 将 JavaScript 函数绑定到ajaxSuccess事件,并检查标头是否存在:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });

没有浏览器正确处理 301 和 302 响应。实际上,该标准甚至说他们应该 “透明地” 处理它们,这对于 Ajax Library 供应商来说是一个巨大的麻烦。在Ra-Ajax 中,我们被迫使用 HTTP 响应状态代码 278(只是一些 “未使用” 的成功代码)来透明地处理来自服务器的重定向。

这真的让我很烦,如果这里有人在 W3C 中有一些 “拉”,我将不胜感激,您可以让 W3C 知道我们确实需要自己处理 301 和 302 代码...! ;)

最终实现的解决方案是对 Ajax 调用的回调函数使用包装器,并在此包装器中检查返回的 HTML 块上是否存在特定元素。如果找到该元素,则包装器将执行重定向。如果不是,则包装将调用转发给实际的回调函数。

例如,我们的包装函数类似于:

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

然后,在进行 Ajax 调用时,我们使用了类似以下内容:

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

这对我们有用,因为所有 Ajax 调用总是在 DIV 元素内返回 HTML,该 DIV 元素用于替换页面的一部分。另外,我们只需要重定向到登录页面。

我喜欢蒂默兹的方法,加上一点柠檬味。如果在期望JSON时曾经返回text / html 的 contentType ,则很可能将您重定向。就我而言,我只是简单地重新加载页面,然后将其重定向到登录页面。哦,检查一下 jqXHR 的状态是否为 200,这似乎很愚蠢,因为您使用的是错误函数,对吗?否则,合法的错误情况将迫使迭代重载(哎呀)

$.ajax(
   error:  function (jqXHR, timeout, message) {
    var contentType = jqXHR.getResponseHeader("Content-Type");
    if (jqXHR.status === 200 && contentType.toLowerCase().indexOf("text/html") >= 0) {
        // assume that our login has expired - reload our current page
        window.location.reload();
    }

});

使用低级$.ajax()调用:

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

尝试以下重定向:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}

我只想分享我的方法,因为这可能会帮助某人:

我基本上包括一个 JavaScript 模块,该模块处理身份验证之类的内容,例如显示用户名,在这种情况下,还处理重定向到登录页面的情况

我的情况:我们基本上有一个 ISA 服务器,在这两个服务器之间侦听所有请求, 并以 302 和登录页面的位置标头作为响应

在我的 JavaScript 模块中,我最初的方法

$(document).ajaxComplete(function(e, xhr, settings){
    if(xhr.status === 302){
        //check for location header and redirect...
    }
});

问题(这里已经提到了很多问题)是浏览器自己处理重定向,因此我的ajaxComplete回调从未被调用过,而是得到了已经重定向的 Login 页面响应 ,显然是status 200 。问题:您如何检测成功的 200 响应是您的实际登录页面还是其他任意页面?

解决方案

由于无法捕获 302 重定向响应,因此在登录页面上添加了LoginPage标头,其中包含登录页面本身的 URL。现在,在模块中,我侦听标头并进行重定向:

if(xhr.status === 200){
    var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage");
    if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){
        window.location.replace(loginPageRedirectHeader);
    }
}

... 就像魅力:)。您可能想知道为什么我将 URL 包含在LoginPage标头中... 基本上是因为我找不到确定从xhr对象进行自动位置重定向而导致的GET ...

我知道这个主题很旧,但是我将给出我已经找到并在此之前描述的另一种方法。基本上,我将 ASP.MVC 与 WIF 一起使用(但这对于本主题的上下文并不真正重要 - 无论使用哪种框架,答案都是足够的。线索保持不变 - 在执行 Ajax 请求时处理与身份验证失败相关的问题)

下面显示的方法可以应用于所有开箱即用的 ajax 请求(如果它们显然没有重新定义 beforeSend 事件)。

$.ajaxSetup({
    beforeSend: checkPulse,
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        document.open();
        document.write(XMLHttpRequest.responseText);
        document.close();
    }
});

在执行任何 ajax 请求之前,将调用CheckPulse方法(可以是最简单的控制器方法):

[Authorize]
public virtual void CheckPulse() {}

如果用户未通过身份验证(令牌已过期),则无法访问此方法(受Authorize属性保护)。由于框架处理身份验证,而令牌到期,因此将 http 状态 302 放入响应中。如果您不希望浏览器透明地处理 302 响应,请在 Global.asax 中捕获它并更改响应状态 - 例如更改为 200 OK。另外,添加标头,它指示您以特殊方式(稍后在客户端)处理此类响应:

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302
        && (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
    {                
        Context.Response.StatusCode = 200;
        Context.Response.AddHeader("REQUIRES_AUTH", "1");
    }
}

最后,在客户端检查此类自定义标头。如果存在 - 应当完全重定向到登录页面(在我的情况下, window.location替换为请求中的 url,由我的框架自动处理)。

function checkPulse(XMLHttpRequest) {
    var location = window.location.href;
    $.ajax({
        url: "/Controller/CheckPulse",
        type: 'GET',
        async: false,
        beforeSend: null,
        success:
            function (result, textStatus, xhr) {
                if (xhr.getResponseHeader('REQUIRES_AUTH') === '1') {
                    XMLHttpRequest.abort(); // terminate further ajax execution
                    window.location = location;
                }
            }
    });
}

我这样解决了这个问题:

添加一个中间件来处理响应,如果它是 ajax 请求的重定向,则使用重定向 URL 将响应更改为正常响应。

class AjaxRedirect(object):
  def process_response(self, request, response):
    if request.is_ajax():
      if type(response) == HttpResponseRedirect:
        r = HttpResponse(json.dumps({'redirect': response['Location']}))
        return r
    return response

然后在 ajaxComplete 中,如果响应包含重定向,则它必须是重定向,因此请更改浏览器的位置。

$('body').ajaxComplete(function (e, xhr, settings) {
   if (xhr.status == 200) {
       var redirect = null;
       try {
           redirect = $.parseJSON(xhr.responseText).redirect;
           if (redirect) {
               window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
           }
       } catch (e) {
           return;
       }
   }
}

我认为处理此问题的更好方法是利用现有的 HTTP 协议响应代码,尤其是401 Unauthorized

这是我解决的方法:

  1. 服务器端:如果会话过期,并且请求是 ajax。发送 401 响应码标头
  2. 客户端:绑定到 ajax 事件

    $('body').bind('ajaxSuccess',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    }).bind('ajaxError',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    });

IMO 这是更通用的,您没有编写新的自定义规范 / 标题。您也不必修改任何现有的 ajax 调用。

编辑:根据下面的 @Rob 的注释,401(身份验证错误的 HTTP 状态代码)应作为指示符。有关更多详细信息,请参见403 禁止与 401 未经授权的 HTTP 响应 。话虽如此,某些 Web 框架同时使用 403 进行身份验证和授权错误 - 因此请进行相应调整。谢谢罗伯。