重定向在异步请求中的处理实现

问题描述

在Web请求某个页面时,我们需要判断当前用户是否有访问权限,如果当前用户尚未登录, 一般会跳转到登录页面,在登录页面登录成功之后,再跳回到原来的页面。如果都是普通 请求,跳转没有什么问题,但是如果请求是以ajax方式发送到后台,后台简单的返回重定 向,前端就有可能在当前的div中显示登录窗口,而不是真正跳转到登录页。这个不是我们 想要的结果。

处理办法

主要涉及到前后台的配合。

后台处理

后台在遇到ajax请求时,不再返回redirect信息,而是返回一个json信息。为了区别于正 常结果,使用了500的返回码。

目前这块在uliweb 0.2版本中已经自动支持。它会根据请求是否为ajax方式来返回普通的 重定向结果,或json结果。返回的json结果为:

{'success':False, 'redirect':'xxxxxxx'}

响应码为500。

在缺省情况下,这种处理是自动的。如果你不想使用这种机制,则需要在启动代码时,在 创建application实例时传入 xhr_redirect_json=False 的参数。如修改wsgi_handler.py 中的:

application = make_application(apps_dir=apps_dir)

为:

application = make_application(apps_dir=apps_dir, dispatcher_kwargs={'xhr_redirect_json':False})

前台处理

前台可以考虑每个请求都去判断出错的情况。但是因为我主要是使用jquery,所以给出jquery 中更通用的做法。

一般在uliweb项目中,引用jquery时会使用 {{use "jquery"}} ,那么它会在生成页面时 查找settings.ini中的 [UI_CONFIG] 中是否有 jquery_bootstrap 的定义。如果有, 会把settings.ini中的值写到模板中。所以,这个配置就是在引入jquery后,可以由用户 来进行必要的初始化的一个配置。缺省情况下它是:

jquery_bootstrap = "<script>$.ajaxSetup({cache:false, traditional:true});</script>"

然后我们可以根据需要修改这个配置。

为了方便,我把处理写在一个js文件中,比如叫jsinit.js。然后你可以放在某个app的static 目录下。它的内容是:

function updateURLParameter(url, param, paramVal)
{
    var TheAnchor = null;
    var newAdditionalURL = "";
    var tempArray = url.split("?");
    var baseURL = tempArray[0];
    var additionalURL = tempArray[1];
    var temp = "";

    if (additionalURL) 
    {
        var tmpAnchor = additionalURL.split("#");
        var TheParams = tmpAnchor[0];
            TheAnchor = tmpAnchor[1];
        if(TheAnchor)
            additionalURL = TheParams;

        tempArray = additionalURL.split("&");

        for (i=0; i<tempArray.length; i++)
        {
            if(tempArray[i].split('=')[0] != param)
            {
                newAdditionalURL += temp + tempArray[i];
                temp = "&";
            }
        }        
    }
    else
    {
        var tmpAnchor = baseURL.split("#");
        var TheParams = tmpAnchor[0];
            TheAnchor  = tmpAnchor[1];

        if(TheParams)
            baseURL = TheParams;
    }

    if(TheAnchor)
        paramVal += "#" + TheAnchor;

    var rows_txt = temp + "" + param + "=" + paramVal;
    return baseURL + "?" + newAdditionalURL + rows_txt;
}

$.ajaxSetup({
    cache:false, 
    traditional:true,
    error:function(jqXHR, textStatus, errorThrown){
        var m = $.parseJSON(jqXHR.responseText);
        if (!m.success && m.redirect){
            var login = /\/login\b/;
            var url = m.redirect;
            //Test if login, then replace next parameter
            if (login.test(m.redirect)){
                url = updateURLParameter(m.redirect, 'next', window.location.href);
            }
            window.location.href = url;
        }
    }
});

其中 updateURLParameter 是一个函数,它的作用是替換URL中的某个参数。为什么需要 这个?这个和uliweb的机制有关。如果你使用 uliweb.contrib.authrequire_login 进行登录检查,那么它会在跳转到登录页面时使用:

/login?next=/path/to/origin/path

因此,当登录成功,可以根据 next 的值再跳回原来的地址上。

但是这里存在一个问题。因为这个next的值 是ajax请求的URL 。可能你要说,这有什 么关系?问题就是,登录成功,你返回的页面将只是ajax请求处理后的结果,而不是原始 的操作页面。ajax的URL只是这个页面中的某个操作,它并不是原来的访问页面。所以,我 们要获得原始的可访问的页面URL,然后把next替換成这个地址。这样,我们并不是实现了 在登录后,再次执行原始的ajax请求,而是跳到合适的页面,让用户再操作一遍。

了解这个处理很重要,因为跳转多次后,原来的ajax请求有可能根本不能直接运行的,比如 使用了POST发送数据,跳转之后数据已经丢失,再提交也是错误。更何况,跳转的页面不 是希望的HTML的页面,只是某个处理后的结果。

所以我们先定义了一个 updateURLParameter 函数用来替換 next 参数。在jquery中没有 直接可用的函数。这个函数是从stackoverflow上找到的。

那么,我们还假设,你使用的是uliweb的require_login的机制,所以才会去替換next值。 不然,你要根据你的应用去考虑如何处理重定向。

这里我们只对登录重定向作了特殊处理,也很难想象还有哪些其它的情况会是ajax请求,但是 后台会重定向。

为了确保是登录URL,我们还对重向定的URL进行了检查,看是否包含 /login 这样的串。 如果有,则替換 next 参数。参数值,我们使用了 window.location.href 。这里我们 假设了ajax请求没有修改URL,所以得到的URL应该就是还没有进行ajax处理的URL,正是 我们想要跳回来的地址。

我们把上面的处理放在了 $.ajaxSetup 这样做为一个缺省的处理。

我们把jsinit.js的引入加到settings.ini中去:

[UI_CONFIG]
jquery_bootstrap = """<script src="/static/jqinit.js"></script>"""

经过前后台的处理之后,前后的处理基本上是透明的。

因为有许多的假设,所以上述的处理仅供参考。