【问题标题】:How can I send request again in response interceptor?如何在响应拦截器中再次发送请求?
【发布时间】:2013-09-05 13:51:26
【问题描述】:

我在我的应用程序中创建了一个拦截器来检测会话丢失(服务器发送 HTTP 419)。在这种情况下,我需要从服务器请求一个新的会话,然后我想再次自动发送原始请求。
也许我可以将请求保存在请求拦截器中,然后再次发送,但可能有更简单的解决方案。

请注意,我必须使用特定的网络服务来创建会话。

angular.module('myapp', [ 'ngResource' ]).factory(
    'MyInterceptor', 
    function ($q, $rootScope) {
        return function (promise) {
            return promise.then(function (response) {
                // do something on success
                return response;
            }, function (response) {
                if(response.status == 419){
                    // session lost
                    // create new session server-side
                    // Session.query();
                    // then send current request again
                    // ???
                }
                return $q.reject(response);
            });
        };
    }).config(function ($httpProvider) {
        $httpProvider.responseInterceptors.push('MyInterceptor');
    });

【问题讨论】:

    标签: angularjs interceptor


    【解决方案1】:

    这是我为感兴趣的人使用 Promise 的解决方案。基本上您需要请求一个新会话,并在发送与原始请求相对应的新请求之前等待响应(使用 response.config)。通过返回承诺 $http(response.config),您可以确保响应将被视为原始请求。
    (语法可能不是最好的,因为我是新的承诺)

    angular.module('myapp', [ 'ngResource' ]).factory(
        'MyInterceptor', 
        function ($q, $rootScope) {
            return function (promise) {
                return promise.then(function (response) {
                    // do something on success
                    return response;
                }, function (response) {
                    if(response.status == 419){
                        // session lost
                        var Session = $injector.get('Session');
                        var $http = $injector.get('$http');
                        // first create new session server-side
                        var defer = $q.defer();
                        var promiseSession = defer.promise; 
                        Session.query({},function(){
                            defer.resolve();
                        }, function(){
                           // error
                           defer.reject();
                        });       
                        // and chain request
                        var promiseUpdate = promiseSession.then(function(){
                            return $http(response.config);
                        });
                        return promiseUpdate;
                    }
                    return $q.reject(response);
                });
            };
        }).config(function ($httpProvider) {
            $httpProvider.responseInterceptors.push('MyInterceptor');
        });
    

    【讨论】:

    【解决方案2】:

    httpInterceptorresponseError 方法必须是这样的:

    responseError: function (response) {
      // omit the retry if the request is made to a template or other url
      if (response.config.apiCal === true) {
        if (response.status === 419) {
          var deferred = $q.defer();
          // do something async: try to login.. rescue a token.. etc.
          asyncFuncionToRecoverFrom419(funcion(){
            // on success retry the http request
            retryHttpRequest(response.config, deferred);
          });
          return deferred.promise;
        } else {
          // a template file...
          return response;
        }
      }
    }
    

    魔法发生在这里:

    function retryHttpRequest(config, deferred){
      function successCallback(response){
        deferred.resolve(response);
      }
      function errorCallback(response){
        deferred.reject(response);
      }
      var $http = $injector.get('$http');
      $http(config).then(successCallback, errorCallback);
    }
    

    【讨论】:

    • 谢谢你让我开心!
    【解决方案3】:

    您走在正确的道路上,您基本上将请求存储在队列中,并在重新建立会话后重试。

    查看这个流行的模块:angular http auth (https://github.com/witoldsz/angular-http-auth)。在本模块中,它们会拦截 401 响应,但您可以根据这种方法为您的解决方案建模。

    【讨论】:

    • 好的,感谢您提供有趣的链接。似乎 $http(response.config) 正在工作。我要测试一下。
    【解决方案4】:

    或多或少相同的解决方案,用打字稿翻译:

    /// <reference path="../app.ts" />
    /// <reference path="../../scripts/typings/angularjs/angular.d.ts" />
    
    class AuthInterceptorService {
    
        static serviceId: string = "authInterceptorService";
    
        constructor(private $q: ng.IQService, private $location: ng.ILocationService, private $injector, private $log: ng.ILogService, private authStatusService) {}
    
        // Attenzione. Per qualche strano motivo qui va usata la sintassi lambda perché se no ts sbrocca il this.
        public request = (config: ng.IRequestConfig) => {
    
            config.headers = config.headers || {};
    
            var s: AuthStatus = this.authStatusService.status;
            if (s.isAuth) {
                config.headers.Authorization = 'Bearer ' + s.accessToken;
            }
    
            return config;
        }
    
        public responseError = (rejection: ng.IHttpPromiseCallbackArg<any>) => {
    
            if (rejection.status === 401) {
    
                var that = this;
    
                this.$log.warn("[AuthInterceptorService.responseError()]: not authorized request [401]. Now I try now to refresh the token.");
    
                var authService: AuthService = this.$injector.get("authService");
                var $http: ng.IHttpService = this.$injector.get("$http");
    
                var defer = this.$q.defer();
                var promise: ng.IPromise<any> = defer.promise.then(() => $http(rejection.config));
    
                authService
                    .refreshAccessToken()
                        .then((response) => {
    
                            that.$log.info("[AuthInterceptorService.responseError()]: token refreshed succesfully. Now I resend the original request.");
    
                            defer.resolve();
                        },
                        (err) => {
    
                            that.$log.warn("[AuthInterceptorService.responseError()]: token refresh failed. I need to logout, sorry...");
    
                            this.authStatusService.clear();
                            this.$location.path('/login');
                        });
    
                return promise;
            }
    
            return this.$q.reject(rejection);
        }
    }
    
    // Update the app variable name to be that of your module variable
    app.factory(AuthInterceptorService.serviceId,
        ["$q", "$location", "$injector", "$log", "authStatusService", ($q, $location, $injector, $log, authStatusService) => { 
            return new AuthInterceptorService($q, $location, $injector, $log, authStatusService)
        }]);
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 2018-03-28
      • 1970-01-01
      • 2020-09-25
      • 2020-03-15
      • 2021-11-02
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 2020-10-22
      相关资源
      最近更新 更多