javascript - Backbone.Sync 与 Safari 的问题

标签 javascript jquery backbone.js backbone-routing jquery-ajaxq

我有一个主干应用程序,我在其中使用 DualStorage 并为 Backbone 实现我自己的同步。我已经实现了自己的同步,因为在我的 API 中,它需要在每个请求的 header 中发送身份验证 token 。如果此身份验证 token 不存在或无效,则 API 将返回 401 错误。

我的应用程序有两个选项卡,当您单击其中一个选项卡或 时,它会将路线从/#gueSTList 切换到/#ticketlist,反之亦然。此问题仅在切换选项卡时发生,而不是在转到应用程序中的任何其他路由时发生。这就是这个问题让我感到非常奇怪的地方,只有这两个请求失败并且所有同步操作都应该被覆盖。

我遇到的问题似乎只存在于 Safari 中,而在 Chrome 或 Firefox 中不存在,但是由于这主要在 iPad 上运行,所以我不能忽视这个问题。

这就是当前的问题

1.) 登录系统,一切正常 UI 由 API 数据填充 2.) 单击工单列表选项卡,系统将注销您。这是因为 API 返回 401,因为请求中不存在身份验证 token (同样仅在 Safari 中)

[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (ticketlist, line 0)

下面是我的 Backbone.sync 代码。

/*
 * Store a version of Backbone.sync to call from the
 * modified version we create
 */
var _nativeSync = Backbone.sync;

Backbone.sync = function (method, model, options) {
    /*
     * The jQuery `ajax` method includes a 'headers' option
     * which lets you set any headers you like
     */

    if(CheckinApp.getSession().isAuthenticated() !== false) {
        /*
         * Set the 'Authorization' header and get the access
         * token from the `auth` module
         */
        options.headers = {
            'Authorization': 'Token ' +     CheckinApp.getSession().getAuthorizationToken()
        }

    }

    /*
     * Call the stored original Backbone.sync method with
     * extra headers argument added
     */
    _nativeSync(method, model, options);
};

我唯一担心的是,这可能会因使用 DualStorage 而产生冲突,因为我知道它也会覆盖 Backbone.sync 方法。为了使其正常工作,我必须在 DualStorage 之后包含我的同步,如下所示。

<script type="text/javascript" src="js/vendor/backbone.dualstorage.min.js"></script>
<script type="text/javascript" src="js/plugins/backbone.sync.js"></script>

我还在 API 端转储了请求的 header ,并且可以看到,从 Safari 发出此特定请求时缺少授权 token ,但在 chrome 或 firefox 发出的同一请求中却没有。

[Tue Apr 07 14:49:08.677473 2015] [:error] [pid 16743] [client 71.181.125.154:64016] <pre>Array\n(\n    
[Host] => jcrawford.heytix.com\n    
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.78.2 (KHTML, like Gecko) Version/6.1.6 Safari/537.78.2\n    
[Accept] => application/json, text/javascript, */*; q=0.01\n    [
Referer] => http://jcrawford.heytix.com/guestlist/\n    
[X-Requested-With] => XMLHttpRequest\n    
[Authorization] => Token 951ba59c833a80e4ddaf72ee6b3d9143\n    
[Accept-Language] => en-us\n    [Accept-Encoding] => gzip, deflate\n    
[Cookie] => 'removed from output'    
[Connection] => keep-alive\n)\n</pre>, referer: http://jcrawford.heytix.com/guestlist/

[Tue Apr 07 14:49:12.027279 2015] [:error] [pid 16743] [client 71.181.125.154:64016] <pre>Array\n(\n    
[Host] => jcrawford.heytix.com\n    
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.78.2 (KHTML, like Gecko) Version/6.1.6 Safari/537.78.2\n    
[Accept] => application/json, text/javascript, */*; q=0.01\n    
[Referer] => http://jcrawford.heytix.com/guestlist/\n    
[Accept-Encoding] => gzip, deflate\n    
[X-Requested-With] => XMLHttpRequest\n    
[Accept-Language] => en-us\n    
[Cookie] => 'removed from output'
[Connection] => keep-alive\n)\n</pre>, referer: http://jcrawford.heytix.com/guestlist/

[Tue Apr 07 14:49:12.027565 2015] [:error] [pid 16743] [client 71.181.125.154:64016] HTTP 401 (GET /api/events/13044/guestlist), referer: http://jcrawford.heytix.com/guestlist/

这就是我使用 Chrome 和/或 Firefox 得到的结果。

[Tue Apr 07 14:57:38.686859 2015] [:error] [pid 17630] [client 71.181.125.154:65109] <pre>Array\n(
[Host] => jcrawford.heytix.com
[Connection] => keep-alive
[Cache-Control] => max-age=0
[Accept] => application/json, text/javascript, */*; q=0.01
[X-Requested-With] => XMLHttpRequest
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36
[Authorization] => Token 951ba59c833a80e4ddaf72ee6b3d9143
[Referer] => http://jcrawford.heytix.com/guestlist/
[Accept-Encoding] => gzip, deflate, sdch
[Accept-Language] => en-US,en;q=0.8
[Cookie] =>  'removed from output'
)</pre>, referer: http://jcrawford.heytix.com/guestlist/


[Tue Apr 07 14:57:44.001465 2015] [:error] [pid 17492] [client 71.181.125.154:65106] <pre>Array\n(\n    
[Host] => jcrawford.heytix.com\n    
[Connection] => keep-alive\n    
[Accept] => application/json, text/javascript, */*; q=0.01\n    
[X-Requested-With] => XMLHttpRequest\n    
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36\n   
[Authorization] => Token 951ba59c833a80e4ddaf72ee6b3d9143\n    
[Referer] => http://jcrawford.heytix.com/guestlist/\n    
[Accept-Encoding] => gzip, deflate, sdch\n   
[Accept-Language] => en-US,en;q=0.8\n    
[Cookie] => 'removed from output'
)\n</pre>, referer: http://jcrawford.heytix.com/guestlist/

正如您从上面的日志记录中看到的,授权 token 是通过 Firefox/Chrome 传递的,但不是通过 Safari 传递的。我已向同步方法添加了日志记录,它在控制台中指出用户在查询 API 之前已通过身份验证,然后重定向到登录页面。

[Log] sync called, isAuthenticated: true (backbone.sync.js, line 12)
[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (ticketlist, line 0)

我在 Safari 开发人员工具/控制台等中没有看到任何其他错误或任何内容。该应用程序向 API 发出请求,获取 401(如预期的那样,没有 token ),然后将用户从 Backbone 中注销应用程序并重定向到登录页面。当没有 token 存在时,这种行为是预期的,问题是为什么这不会为这些特定路由传递 token ?只有这些路由导致了问题,所有其他路由似乎在 UI 中都工作得很好。

我还将提供我的路由器,以便您可以看到发生了什么,请记住,我在大部分情况下使用事件来执行实际路由,因此如果您需要查看任何其他代码,请告诉我.

CheckinApp.Routers.Default = Backbone.Router.extend({
    view: null,

    public_routes: ['login'],

    routes:{
        "":"eventlist",
        "login": "login",
        "guestlist": "guestlist",
        "ticketlist":"ticketlist",
        "managerslist":"managerslist",
        "events": "eventlist",
        "organize(/)(:action)": "displayOrganize",
        "eventreport(/)(:event_id)": "eventreport",
        "venuereport(/)(:venue_name)": "venuereport"
    },

    initialize:function (options) {
        this.view = options.view;
        Backbone.history.start();
    },

    guestlist:function () {
        CheckinApp.getVent().trigger('main:renderListView', {title: 'Guest List', type: 'ticket', tab_hash: '#guestlist'});
    },
    ticketlist:function () {
        CheckinApp.getVent().trigger('main:renderListView', {title: 'Ticket List', type: 'ticket', tab_hash: '#ticketlist'});
    },
    managerslist:function () {
        CheckinApp.getVent().trigger('main:renderListView', {title: 'Managers List', type: 'ticket', tab_hash: '#managerslist'});
    },
    eventlist: function() {
        var vent = CheckinApp.getVent();
        vent.trigger('main:renderListView', {title: 'Todays Events', type: 'event'});
        vent.trigger('tabs:remove');
    },
    eventreport: function() {
        var collection = new CheckinApp.Collections.EventReport({"event_id": 13044});
        var view = new CheckinApp.Views.EventReport({collection: collection});
        view.render();
    },
    venuereport: function(venue_name) {
        var collection = new CheckinApp.Collections.VenueReport([], {"venue_name": 'borgata'});
        var view = new CheckinApp.Views.VenueReport({collection: collection});
        var modal = new Backbone.BootstrapModal({
            content: view,
            title: ' ',
            animate: true
        });
        modal.open();
        //view.render();
    },

    login: function() {
        var view = new CheckinApp.Views.Login({});
        view.render();
    },

    before: function (route, params) {

        if($.cookie('CheckinApp') && CheckinApp.getSession().isAuthenticated() == false) {
            CheckinApp.setSessionFromCookie(JSON.parse($.cookie('CheckinApp')));
        }
        var hasAccess = CheckinApp.getSession().isAuthenticated(); // If cookie exists they are logged in..

        if (!hasAccess) {
            this.navigate('login', true);
        } else {
            if(route == 'login') {
                this.navigate('', true);
                return false;
            }
        }
        if((_.contains(this.public_routes, route) === false)) {
            return hasAccess; //return true if you want to proceed to routes else return false
        }
    },

    after: function(route, params) {
        if(route == 'logout') return false;
        else {
            CheckinApp.updateCookie();
            return true;
        }
    }
});

最后,这是告诉 jQuery 监听 401 并在发生时注销用户的代码。

$.ajaxSetup({
    statusCode: {
        401: function () {
            CheckinApp.clearSession();
            Backbone.history.navigate('#login', true);
        }
    }
});

我还更进一步,在之前的路由中添加了一堆 console.log 语句,并提出了这个。看起来可能是对这些特定路由执行了某些操作导致身份验证丢失?

[Log] sync :  isAuthenticated = true (backbone.sync.js, line 12)
[Log] sync: url = http://jcrawford.heytix.com/guestlist/checkin/api/events/13044/guestlist/ (backbone.sync.js, line 13)
[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (guestlist, line 0)
[Log] before : isAuthenticated: false (default.js, line 60)
[Log] cookie: undefined (default.js, line 61)
[Log] before : hasAccess = false (default.js, line 66)
[Log] before : hasAccess = false, going to login page (default.js, line 68)
[Log] before : going to route login (default.js, line 76)

正如您所看到的,在对数据发出 AJAX 请求的行之前,它显示已验证,然后请求失败并显示未验证。

这次我的同步方法记录了更多内容

sync :  isAuthenticated = true (backbone.sync.js, line 12)
sync: url = http://jcrawford.heytix.com/guestlist/checkin/api/events/13044/guestlist/ (backbone.sync.js, line 13)
User is Authenticated (backbone.sync.js, line 15)
options: {"parse":true,"headers":{"Authorization":"Token 951ba59c833a80e4ddaf72ee6b3d9143"}} (backbone.sync.js, line 24)

正如您在上面所看到的,选项是在 header 上设置的,但由于某种原因,在使用 Safari Backbone 时,不会通过同步请求发送这些 header 。

根据一些建议(以及下面的建议),我尝试修改 jQuery 的 $.ajaxSetup,但收到的结果与我当前遇到的结果完全相同。

$.ajaxSetup({
    headers: function() {
        var token = '';
        if(CheckinApp) {
            var session = CheckinApp.getSession();
            if(session) {
                token = CheckinApp.getSession().getAuthorizationToken();
            }
        }
        return {
            "Authorization": "Token " + token
        };
    },
    statusCode: {
        401: function () {
            CheckinApp.clearSession();
            Backbone.history.navigate('#login', true);
        }
    }
});

任何帮助将不胜感激。

最佳答案

发生此问题的唯一原因是我的门票收集 URL 包含尾随/

使用尾随/就位,Safari 会向带有尾随斜杠的 URL 发送飞行前请求,并收到 302 Found。然后,它会向 URI 发出不带尾部斜杠的请求,并收到 401,因为第二个请求未将授权 token 传递给 API。

我不太确定这是 Backbone 还是 jQuery 的问题,但无论是哪一个,它似乎都不喜欢尾随斜杠,因为删除它可以解决 Safari 问题。

关于javascript - Backbone.Sync 与 Safari 的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29472065/

相关文章:

backbone.js - 如何将 this.save 绑定(bind)到主干.js 模型更改事件?

php - 使用 php 调用外部 Javascript

javascript - 如何使用angular-ui bootstrap在angularjs中实现服务器端分页。?

javascript - 如何使用自定义类一次重置 jquery mobile 中的多个下拉菜单

javascript - Access-Control-Allow-Origin 不允许 Origin null

javascript - 如何在新 View 渲染之前关闭所有事件

javascript - 如何在使用 d3.behavior.zoom() 平移时删除 "jumpiness"

javascript - 来自对象的特定子对象

jquery - jqgrid 卡住列可以与树形网格一起使用吗?

javascript - 注册MDL组件