$.post()
方法通过Ajax调用servlet,然后使用返回的HTML片段替换用户当前页面中的一个 div
元素。但是,如果会话超时,服务器会发送重定向指令将用户发送到登录页面。在这种情况下,jQuery会将 div
元素替换为登录页面的内容,使用户看到一个罕见的场景。如何在使用jQuery 1.2.6时处理来自Ajax调用的重定向指令?
$.post()
方法通过Ajax调用servlet,然后使用返回的HTML片段替换用户当前页面中的一个 div
元素。但是,如果会话超时,服务器会发送重定向指令将用户发送到登录页面。在这种情况下,jQuery会将 div
元素替换为登录页面的内容,使用户看到一个罕见的场景。我阅读了这个问题,并实现了已经提到的方法,将响应HTTP状态码设置为278,以避免浏览器透明地处理重定向。尽管这种方法有效,但我还是有点不满意,因为它有点像一个hack。
经过更多的研究,我放弃了这种方法,改用JSON。在这种情况下,所有针对AJAX请求的响应都具有状态码200,响应正文包含在服务器上构建的JSON对象。然后客户端的JavaScript可以使用JSON对象来决定它需要做什么。
我遇到了类似于你的问题。我执行了一个AJAX请求,它有两个可能的响应:一个将浏览器重定向到一个新页面,另一个用一个新的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",包含两个成员:data.redirect
和data.form
。我发现这种方法要好得多。
向响应添加自定义标头:
public ActionResult Index(){
if (!HttpContext.User.Identity.IsAuthenticated)
{
HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
}
return View();
}
将JavaScript函数绑定到 ajaxSuccess
事件,并检查标头是否存在:
$(document).ajaxSuccess(function(event, request, settings) {
if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
window.location = '/';
}
});
最终实现的解决方案是使用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,我们使用该元素替换页面上的一部分。此外,我们只需要重定向到登录页面。
我喜欢Timmerz的方法,稍微加点柠檬味。如果你期望得到JSON格式但实际上返回了text/html类型,很可能是因为发生了重定向。在我的情况中,我只需简单地重新加载页面,它就会被重定向到登录页面。同时要检查jqXHR状态是否为200,这似乎很愚蠢,因为你正在error函数中,对吗?否则,合法的错误情况将强制进行迭代式重新加载(糟糕)。
$.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
回调从未被调用,而是得到了已经重定向的登录页面的响应,显然这是一个状态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
请求的URL的方法...
我知道这个主题很久了,但是我会提供另一种方法,之前在这里描述过。基本上,我正在使用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;
}
}
});
}
我认为更好的处理方式是利用现有的HTTP协议响应代码,特别是401 Unauthorized
。
这是我的解决方法:
客户端:绑定到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';
}
});
在我看来,这种方法更加通用,而且你不需要编写新的自定义规范/头文件。此外,您也不需要修改任何现有的ajax调用。
编辑:根据@Rob下面的评论,401(身份验证错误的HTTP状态代码)应该是指示器。有关更多详细信息,请参见403 Forbidden vs 401 Unauthorized HTTP responses。尽管如此,一些Web框架将403用于身份验证和授权错误 - 因此请相应地进行调整。谢谢,Rob。
我是这样解决这个问题的:
添加一个中间件来处理响应,如果响应是一个针对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;
}
}
}
HttpContext.Response.AddHeader
和检查ajaxsetup
的成功是可行的方法。 - LCJ