简体   繁体   English

AngularJS:将服务注入HTTP拦截器(循环依赖)

[英]AngularJS: Injecting service into a HTTP interceptor (Circular dependency)

I'm trying to write a HTTP interceptor for my AngularJS app to handle authentication. 我正在尝试为我的AngularJS应用程序编写HTTP拦截器来处理身份验证。

This code works, but I'm concerned about manually injecting a service since I thought Angular is supposed to handle this automatically: 这段代码有效,但我担心手动注入服务,因为我认为Angular应该自动处理:

    app.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($location, $injector) {
        return {
            'request': function (config) {
                //injected manually to get around circular dependency problem.
                var AuthService = $injector.get('AuthService');
                console.log(AuthService);
                console.log('in request interceptor');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    })
}]);

What I started out doing, but ran into circular dependency problems: 我开始做的事情,但遇到循环依赖问题:

    app.config(function ($provide, $httpProvider) {
    $provide.factory('HttpInterceptor', function ($q, $location, AuthService) {
        return {
            'request': function (config) {
                console.log('in request interceptor.');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    });

    $httpProvider.interceptors.push('HttpInterceptor');
});

Another reason why I'm concerned is that the section on $http in the Angular Docs seem to show a way to get dependencies injected the "regular way" into a Http interceptor. 我担心的另一个原因是Angular Docs中关于$ http部分似乎显示了一种方法来将依赖关系注入“常规方式”到Http拦截器中。 See their code snippet under "Interceptors": 在“拦截器”下查看他们的代码片段:

// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  return {
    // optional method
    'request': function(config) {
      // do something on success
      return config || $q.when(config);
    },

    // optional method
   'requestError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    },



    // optional method
    'response': function(response) {
      // do something on success
      return response || $q.when(response);
    },

    // optional method
   'responseError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    };
  }
});

$httpProvider.interceptors.push('myHttpInterceptor');

Where should the above code go? 上面的代码应该放在哪里?

I guess my question is what's the right way to go about doing this? 我想我的问题是这样做的正确方法是什么?

Thanks, and I hope my question was clear enough. 谢谢,我希望我的问题很清楚。

This is what I ended up doing 这就是我最终做的事情

  .config(['$httpProvider', function ($httpProvider) {
        //enable cors
        $httpProvider.defaults.useXDomain = true;

        $httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) {
            return {
                'request': function (config) {

                    //injected manually to get around circular dependency problem.
                    var AuthService = $injector.get('Auth');

                    if (!AuthService.isAuthenticated()) {
                        $location.path('/login');
                    } else {
                        //add session_id as a bearer token in header of all outgoing HTTP requests.
                        var currentUser = AuthService.getCurrentUser();
                        if (currentUser !== null) {
                            var sessionId = AuthService.getCurrentUser().sessionId;
                            if (sessionId) {
                                config.headers.Authorization = 'Bearer ' + sessionId;
                            }
                        }
                    }

                    //add headers
                    return config;
                },
                'responseError': function (rejection) {
                    if (rejection.status === 401) {

                        //injected manually to get around circular dependency problem.
                        var AuthService = $injector.get('Auth');

                        //if server returns 401 despite user being authenticated on app side, it means session timed out on server
                        if (AuthService.isAuthenticated()) {
                            AuthService.appLogOut();
                        }
                        $location.path('/login');
                        return $q.reject(rejection);
                    }
                }
            };
        }]);
    }]);

Note: The $injector.get calls should be within the methods of the interceptor, if you try to use them elsewhere you will continue to get a circular dependency error in JS. 注意: $injector.get调用应该在拦截器的方法内,如果你试图在别处使用它们,你将继续在JS中获得循环依赖性错误。

You have a circular dependency between $http and your AuthService. $ http和您的AuthService之间存在循环依赖关系。

What you are doing by using the $injector service is solving the chicken-and-egg problem by delaying the dependency of $http on the AuthService. 你通过使用$injector服务正在做的是通过延迟$ http对AuthService的依赖来解决鸡与蛋的问题。

I believe that what you did is actually the simplest way of doing it. 我相信你所做的实际上是最简单的方法。

You could also do this by: 你也可以这样做:

  • Registering the interceptor later (doing so in a run() block instead of a config() block might already do the trick). 稍后注册拦截器(在run()块而不是config()块中执行此操作可能已经完成了这一操作)。 But can you guarantee that $http hasn't been called already? 但是你能保证$ http还没有被调用吗?
  • "Injecting" $http manually into the AuthService when you're registering the interceptor by calling AuthService.setHttp() or something. 当您通过调用AuthService.setHttp()或其他东西注册拦截器时,“将”http“手动注入” AuthService.setHttp()
  • ... ...

I think using the $injector directly is an antipattern. 我认为直接使用$ injector是一个反模式。

A way to break the circular dependency is to use an event: Instead of injecting $state, inject $rootScope. 打破循环依赖的一种方法是使用一个事件:不是注入$ state,而是注入$ rootScope。 Instead of redirecting directly, do 做,而不是直接重定向

this.$rootScope.$emit("unauthorized");

plus

angular
    .module('foo')
    .run(function($rootScope, $state) {
        $rootScope.$on('unauthorized', () => {
            $state.transitionTo('login');
        });
    });

Bad logic made such results 糟糕的逻辑造就了这样的结

Actually there is no point of seeking is user authored or not in Http Interceptor. 实际上,在Http Interceptor中没有用户创作的用户创作。 I would recomend to wrap your all HTTP requests into single .service (or .factory, or into .provider), and use it for ALL requests. 我建议将所有HTTP请求包装成单个.service(或.factory,或.provider),并将其用于所有请求。 On each time you call function, you can check is user logged in or not. 每次调用函数时,都可以检查用户是否登录。 If all is ok, allow send request. 如果一切正常,请允许发送请求。

In your case, Angular application will send request in any case, you just checking authorization there, and after that JavaScript will send request. 在您的情况下,Angular应用程序将在任何情况下发送请求,您只需在那里检查授权,然后JavaScript将发送请求。

Core of your problem 你的问题的核心

myHttpInterceptor is called under $httpProvider instance. myHttpInterceptor$httpProvider实例下$httpProvider Your AuthService uses $http , or $resource , and here you have dependency recursion, or circular dependency. 您的AuthService使用$http$resource ,这里有依赖递归或循环依赖。 If your remove that dependency from AuthService , than you will not see that error. 如果从AuthService删除该依赖AuthService ,则不会发现该错误。


Also as @Pieter Herroelen pointed, you could place this interceptor in your module module.run , but this will be more like a hack, not a solution. 同样正如@Pieter Herroelen指出的那样,你可以将这个拦截器放在你的模块module.run ,但这更像是一个黑客,而不是一个解决方案。

If your up to do clean and self descriptive code, you must go with some of SOLID principles. 如果您需要执行干净且自我描述的代码,则必须遵循一些SOLID原则。

At least Single Responsibility principle will help you a lot in such situations. 在这种情况下,至少单一责任原则将对您有所帮助。

If you're just checking for the Auth state (isAuthorized()) I would recommend to put that state in a separate module, say "Auth", which just holds the state and doesn't use $http itself. 如果您只是检查Auth状态(isAuthorized()),我建议将该状态放在一个单独的模块中,比如“Auth”,它只保存状态并且不使用$ http本身。

app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.interceptors.push(function ($location, Auth) {
    return {
      'request': function (config) {
        if (!Auth.isAuthenticated() && $location.path != '/login') {
          console.log('user is not logged in.');
          $location.path('/login');
        }
        return config;
      }
    }
  })
}])

Auth Module: 认证模块:

angular
  .module('app')
  .factory('Auth', Auth)

function Auth() {
  var $scope = {}
  $scope.sessionId = localStorage.getItem('sessionId')
  $scope.authorized = $scope.sessionId !== null
  //... other auth relevant data

  $scope.isAuthorized = function() {
    return $scope.authorized
  }

  return $scope
}

(i used localStorage to store the sessionId on client side here, but you can also set this inside your AuthService after a $http call for example) (我在这里使用localStorage将sessionId存储在客户端,但你也可以在$ http调用之后在你的AuthService中设置它)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM