如何在jQuery Ajax调用之后pipe理redirect请求
我使用$.post()
来调用使用Ajax的servlet,然后使用生成的HTML片段replace用户当前页面中的div
元素。 但是,如果会话超时,服务器将发送redirect指令以将用户发送到login页面。 在这种情况下,jQuery正在用login页面的内容replacediv
元素,强迫用户的眼睛确实见证一个罕见的场景。
我如何使用jQuery 1.2.6从Ajax调用pipe理redirect指令?
我阅读了这个问题,并实现了将响应状态码设置为278的方法,以避免浏览器透明地处理redirect。 即使这个工作,我有点不满,因为这是一个黑客。
经过更多的挖掘,我放弃了这种方法,并使用JSON 。 在这种情况下,所有对ajax请求的响应都具有状态码200,并且响应正文包含在服务器上构build的JSON对象。 客户端上的JavaScript可以使用JSON对象来决定它需要做什么。
我和你有类似的问题。 我执行一个ajax请求,它有两个可能的响应:一个是将浏览器redirect到一个新页面,另一个是用当前页面replace当前页面上现有的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”在服务器上构build,有两个成员: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 = '/'; } });
没有浏览器正确处理301和302响应。 事实上,标准甚至说他们应该“透明地”处理这些,这对Ajax库供应商来说是一个巨大的麻烦。 在Ra-Ajax中,我们被迫使用HTTP响应状态代码278(只是一些“未使用的”成功代码)来处理来自服务器的透明redirect。
这真的让我很烦恼,如果某个人在W3C有一些“拉”,我会感激你可以让W3C 知道我们真的需要自己处理301和302的代码…! ;)
最终实现的解决scheme是为Ajax调用的callback函数使用包装,并在此包装中检查返回的HTML块中是否存在特定的元素。 如果find元素,则包装器执行redirect。 如果不是,包装器将该呼叫转发到实际的callback函数。
例如,我们的包装函数是这样的:
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来replace页面的一部分。 另外,我们只需要redirect到login页面。
我喜欢Timmerz的方法,柠檬稍微扭曲。 如果您在期待JSON时获得了text / html的返回contentType ,则很可能会被redirect。 在我的情况下,我只是简单地重新加载页面,并将其redirect到login页面。 哦,并检查jqXHR状态是200,这似乎很愚蠢,因为你在错误的function,对不对? 否则,合法的错误情况将强制迭代重新加载(oops)
$.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); } });
试试这个redirect:
if (xmlHttp.code != 200) { top.location.href = '/some/other/page'; }
我只是想分享我的方法,因为这可能会帮助某人:
我基本上包括一个JavaScript模块,处理身份validation的东西,如显示用户名,也处理redirect到login页面 。
我的场景:我们之间基本上有一个ISA服务器,它监听所有的请求, 并在我们的login页面上显示一个302和一个位置标题 。
在我的JavaScript模块中,我最初的方法是类似的
$(document).ajaxComplete(function(e, xhr, settings){ if(xhr.status === 302){ //check for location header and redirect... } });
问题(这里已经提到的很多)是,浏览器自己处理redirect,因此我的ajaxComplete
callback从来没有被调用,而是我得到了已经redirect的login页面的响应 ,显然是一个status 200
。 问题:你如何检测成功的200响应是你的实际login页面还是其他任意页面?
解决scheme
由于我无法捕获302redirect响应,因此我在login页面上添加了一个LoginPage
标题,其中包含login页面本身的URL。 在模块中,我现在听标题并做一个redirect:
if(xhr.status === 200){ var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage"); if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){ window.location.replace(loginPageRedirectHeader); } }
…这就像魅力:)。 您可能想知道为什么我将url包含在LoginPage
标题中…基本上,因为我发现没有办法确定从xhr
对象的自动位置redirect导致的GET
的url …
我知道这个话题是旧的,但我会给我另一种方法,我已经find和先前在这里描述。 基本上我使用ASP.MVC与WIF (但是这对于这个主题的上下文并不重要 – 无论使用哪个框架,答案都是足够的。线索保持不变 – 在执行Ajax请求时处理与authentication失败相关的问题) 。
下面显示的方法可以应用于所有的开箱即用的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() {}
如果用户未通过身份validation(令牌已过期),则无法访问此类方法(由Authorize
属性保护)。 由于框架处理authentication,而令牌到期,它将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"); } }
最后在客户端检查这样的自定义标题。 如果存在的话 – 完全redirect到login页面应该完成(在我的情况下, window.location
被由我的框架自动处理的请求中的urlreplace)。
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。 发送一个401响应docker
-
客户端:绑定到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调用。
编辑:下面的 Per @ Rob的评论,401(authentication错误的HTTP状态代码)应该是指标。 有关更多详细信息,请参阅403 Forbidden与401 Unauthorized HTTP响应 。 有人说,一些Web框架使用403authentication和授权错误 – 所以适应相应。 感谢Rob。
我发现的另一个解决scheme(特别是如果你想设置一个全局行为有用)是使用$.ajaxsetup()
方法与statusCode
属性 。 像其他人一样指出,不要使用redirect状态码( 3xx
),而是使用4xx
并处理redirect客户端。
$.ajaxSetup({ statusCode : { 400 : function () { window.location = "/"; } } });
将400
replace为您要处理的状态码。 就像已经提到的401 Unauthorized
可能是一个好主意。 我使用400
因为它是非常不明确的,我可以使用401
更具体的情况(如错误的login凭据)。 因此,当会话超时并且您处理redirect客户端时,不要直接redirect您的后端应返回4xx
错误代码。 即使有像backbone.js这样的框架,对我来说也是完美的
使用ASP.NET MVC RedirectToAction方法可能会出现此问题。 为了防止窗体显示在div中的响应,你可以简单地做一些Ajax响应filter来input$ .ajaxSetup响应。 如果响应包含MVCredirect,则可以在JS端评估此expression式。 下面是JS的代码示例:
$.ajaxSetup({ dataFilter: function (data, type) { if (data && typeof data == "string") { if (data.indexOf('window.location') > -1) { eval(data); } } return data; } });
如果数据是: “window.location ='/ Acount / Login'”,那么上面的filter会捕获该数据,并进行redirect而不是让数据显示。
我解决这个问题是这样的:
添加一个中间件来处理响应,如果它是一个ajax请求的redirect,请将响应更改为具有redirecturl的正常响应。
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中,如果响应包含redirect,它必须是redirect,所以更改浏览器的位置。
$('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; } }
把弗拉基米尔·普鲁德尼科夫和托马斯·汉森说成是:
- 更改你的服务器端代码来检测它是否是XHR。 如果是,请将redirect的响应代码设置为278.在django中:
if request.is_ajax(): response.status_code = 278
这会使浏览器将响应视为成功,并将其传递给您的Javascript。
- 在你的JS中,确保表单提交是通过Ajax,检查响应代码并在需要时redirect:
$('#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); });
大多数给定的解决scheme使用一个解决方法,使用额外的头或不正确的HTTP代码。 这些解决scheme很可能工作,但感觉有点“哈克”。 我想出了另一个解决scheme。
我们使用configuration为在401响应中redirect(passiveRedirectEnabled =“true”)的WIF。 处理正常请求时,redirect是有用的,但不适用于AJAX请求(因为浏览器不会执行302 /redirect)。
在global.asax中使用以下代码,可以禁用AJAX请求的redirect:
void WSFederationAuthenticationModule_AuthorizationFailed(object sender, AuthorizationFailedEventArgs e) { string requestedWithHeader = HttpContext.Current.Request.Headers["X-Requested-With"]; if (!string.IsNullOrEmpty(requestedWithHeader) && requestedWithHeader.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase)) { e.RedirectToIdentityProvider = false; } }
这可以让你返回401响应的AJAX请求,你的JavaScript可以通过重新加载页面来处理。 重新加载页面将会抛出一个401将被WIF处理(WIF将把用户redirect到login页面)。
一个JavaScript处理401错误的例子:
$(document).ajaxError(function (event, jqxhr, settings, exception) { if (jqxhr.status == 401) { //Forbidden, go to login //Use a reload, WIF will redirect to Login location.reload(true); } });
我有一个简单的解决scheme,适用于我,无需更改服务器代码…只需添加一小豆肉豆蔻…
$(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 ); }; }); });
我检查是否存在html标签,但是您可以更改indexOf来searchlogin页面中存在的唯一string。
尝试
$(document).ready(function () { if ($("#site").length > 0) { window.location = "<%= Url.Content("~") %>" + "Login/LogOn"; } });
把它放在login页面上。 如果它被加载到主页面上的一个div上,它将redirect到login页面。 “#site”是位于除login页面以外的所有页面上的div的id。
<script> function showValues() { var str = $("form").serialize(); $.post('loginUser.html', str, function(responseText, responseStatus, responseXML){ if(responseStatus=="success"){ window.location= "adminIndex.html"; } }); } </script>
我通过在login.php页面中添加以下内容来解决这个问题。
<script type="text/javascript"> if (top.location.href.indexOf('login.php') == -1) { top.location.href = '/login.php'; } </script>
虽然答案似乎适用于人,如果您使用Spring Security我已经发现扩展LoginUrlAuthenticationEntryPoint和添加特定的代码来处理AJAX更强大。 大部分示例拦截所有redirect,而不仅仅是身份validation失败。 这对我所从事的项目来说是不可取的。 如果不希望caching失败的AJAX请求,您可能还需要扩展ExceptionTranslationFilter并覆盖“sendStartAuthentication”方法以删除caching步骤。
示例AjaxAwareAuthenticationEntryPoint:
public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { public AjaxAwareAuthenticationEntryPoint(String loginUrl) { super(loginUrl); } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if (isAjax(request)) { response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself"); } else { super.commence(request, response, authException); } } public static boolean isAjax(HttpServletRequest request) { return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); }
}
来源: 1,2
有些人可能会发现以下有用的:
我希望客户端被redirect到没有授权令牌的任何rest操作的login页面。 由于我的所有rest-actions都是基于Ajax的,所以我需要一个很好的通用方法来redirect到login页面,而不是处理Ajax成功函数。
这就是我所做的:
在任何Ajax请求我的服务器将返回一个JSON 200响应“需要authentication”(如果客户端需要authentication)。
Java(服务器端)中的简单示例:
@Secured @Provider @Priority(Priorities.AUTHENTICATION) public class AuthenticationFilter implements ContainerRequestFilter { private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class); public static final String COOKIE_NAME = "token_cookie"; @Override public void filter(ContainerRequestContext context) throws IOException { // Check if it has a cookie. try { Map<String, Cookie> cookies = context.getCookies(); if (!cookies.containsKey(COOKIE_NAME)) { m_logger.debug("No cookie set - redirect to login page"); throw new AuthenticationException(); } } catch (AuthenticationException e) { context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build()); } } }
在我的Javascript中,我添加了下面的代码:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) { var originalSuccess = options.success; options.success = function(data) { if (data == "NEED TO AUTHENTICATE") { window.location.replace("/login.html"); } else { originalSuccess(data); } }; });
这就是它。
在servlet中你应该把response.setStatus(response.SC_MOVED_PERMANENTLY);
发送您需要redirect的'301'xmlHttp状态…
在$ .ajax函数中,你不应该使用.toString()
函数
if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }
问题是它不是很灵活,你不能决定你想要redirect到哪里。
通过servletredirect应该是最好的方法。 但我仍然无法find正确的方法来做到这一点。
如果你也想传递值,那么你也可以设置会话variables和访问例如:在你的jsp中,你可以写
<% HttpSession ses = request.getSession(true); String temp=request.getAttribute("what_you_defined"); %>
然后,你可以将这个临时值存储在你的javascriptvariables中,然后玩
我没有任何成功的头解决scheme – 他们从来没有拿起我的ajaxSuccess / ajaxComplete方法。 我用Steg的回答与自定义的回应,但我修改了JS方面的一些。 我设置了一个方法,我在每个函数中调用,所以我可以使用标准的$.get
和$.post
方法。
function handleAjaxResponse(data, callback) { //Try to convert and parse object try { if (jQuery.type(data) === "string") { data = jQuery.parseJSON(data); } if (data.error) { if (data.error == 'login') { window.location.reload(); return; } else if (data.error.length > 0) { alert(data.error); return; } } } catch(ex) { } if (callback) { callback(data); } }
它正在使用的示例…
function submitAjaxForm(form, url, action) { //Lock form form.find('.ajax-submit').hide(); form.find('.loader').show(); $.post(url, form.serialize(), function (d) { //Unlock form form.find('.ajax-submit').show(); form.find('.loader').hide(); handleAjaxResponse(d, function (data) { // ... more code for if auth passes ... }); }); return false; }
最后,我通过添加一个自定义的HTTP Header
解决这个问题。 就在响应服务器端的每个请求之前,我将当前请求的url添加到响应的头部。
我的服务器上的应用程序types是Asp.Net MVC
,它有一个很好的地方做到这一点。 在Global.asax
我实现了Application_EndRequest
事件,所以:
public class MvcApplication : System.Web.HttpApplication { // ... // ... protected void Application_EndRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Headers.Add("CurrentUrl",app.Context. Request.CurrentExecutionFilePath); } }
这对我来说是完美的! 现在,在JQuery
$.post
每个响应中,我都有请求的url
以及其他响应标头,这些响应标头是由状态303
,…的POST
方法引起的。
其他重要的是不需要修改服务器端和客户端的代码。
而下一个就是能够获得后置动作的其他信息,比如错误,消息,…这样。
我发布了这个,也许帮助别人:)
我只是想locking整个页面的任何Ajax请求。 @SuperG让我开始了。 这是我最后的结果:
// 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; } });
我想专门检查某些http状态代码,以作出我的决定。 然而,你可以绑定到ajaxError获得除了成功之外的任何东西(也许只有200个?)我可以刚刚写道:
$('body').bind('ajaxError', function(event,request,settings){ window.location.replace("http://mysite.tld/login"); }
I was having this problem on a django app I'm tinkering with (disclaimer: I'm tinkering to learn, and am in no way an expert). What I wanted to do was use jQuery ajax to send a DELETE request to a resource, delete it on the server side, then send a redirect back to (basically) the homepage. When I sent HttpResponseRedirect('/the-redirect/')
from the python script, jQuery's ajax method was receiving 200 instead of 302. So, what I did was to send a response of 300 with:
response = HttpResponse(status='300') response['Location'] = '/the-redirect/' return response
Then I sent/handled the request on the client with jQuery.ajax like so:
<button onclick="*the-jquery*">Delete</button> where *the-jquery* = $.ajax({ type: 'DELETE', url: '/resource-url/', complete: function(jqxhr){ window.location = jqxhr.getResponseHeader('Location'); } });
Maybe using 300 isn't "right", but at least it worked just like I wanted it to.
PS :this was a huge pain to edit on the mobile version of SO. Stupid ISP put my service cancellation request through right when I was done with my answer!
You can also hook XMLHttpRequest send prototype. This will work for all sends (jQuery/dojo/etc) with one handler.
I wrote this code to handle a 500 page expired error, but it should work just as well to trap a 200 redirect. Ready the wikipedia entry on XMLHttpRequest onreadystatechange about the meaning of readyState.
// Hook XMLHttpRequest var oldXMLHttpRequestSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { //console.dir( this ); this.onreadystatechange = function() { if (this.readyState == 4 && this.status == 500 && this.responseText.indexOf("Expired") != -1) { try { document.documentElement.innerHTML = this.responseText; } catch(error) { // IE makes document.documentElement read only document.body.innerHTML = this.responseText; } } }; oldXMLHttpRequestSend.apply(this, arguments); }
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.
这对我工作:
success: function(data, textStatus, xhr) { console.log(xhr.status); }
on success, ajax will get the same status code the browser gets from the server and execute it.