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

我正在尝试为我的 AngularJS 应用程序编写一个 HTTP 拦截器来处理身份验证。

这个代码可以工作,但是我担心手动注入服务,因为我认为 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;
}
};
})
}]);

我刚开始做的,但是遇到了循环依赖问题:

    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');
});

我担心的另一个原因是,Angular Docs 中的 部分的 $http似乎显示了一种将依赖项以“常规方式”注入 Http 拦截器的方法。查看他们在“拦截器”下面的代码片段:

// 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');

上面的代码应该放在哪里?

我想我的问题是怎么做才是正确的?

谢谢,我希望我的问题够清楚了。

46103 次浏览

You have a circular dependency between $http and your AuthService.

使用 $injector服务所做的就是通过延迟 $http 对 AuthService 的依赖性来解决先有鸡还是先有蛋的问题。

I believe that what you did is actually the simplest way of doing it.

你也可以这样做:

  • 稍后注册拦截器(在 run()块而不是 config()块中注册拦截器可能已经达到了这个目的)。但是您能保证 $http 还没有被调用吗?
  • 当您通过调用 AuthService.setHttp()或其他方式注册拦截器时,将 $http 手动注入 AuthService。
  • ...

Bad logic made such results

实际上,在 Http 拦截器中没有查找是否由用户编写的问题。我建议将所有 HTTP 请求封装为单个请求。服务(或。工厂,或进入。) ,并将其用于所有请求。每次调用函数时,都可以检查用户是否登录。如果一切正常,允许发送请求。

在您的情况下,Angular 应用程序将在任何情况下发送请求,您只需检查授权,然后 JavaScript 将发送请求。

你的核心问题

$httpProvider实例下调用 myHttpInterceptor。你的 AuthService使用 $http,或者 $resource,这里你有依赖递归,或者循环依赖。如果从 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.

如果你做清洁和自我描述的代码,你必须去一些 SOLID 的原则。

在这种情况下,至少单一责任原则会对你有很大的帮助。

这就是我最后所做的

  .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);
}
}
};
}]);
}]);

注意: $injector.get调用应该在拦截器的方法中,如果您试图在其他地方使用它们,您将继续在 JS 中获得循环依赖性错误。

我认为直接使用 $注入器是一种反模式。

A way to break the circular dependency is to use an event: 不要注入 $state,而是注入 $rootScope。 不要直接重定向,而是

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

plus

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

如果您只是检查 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;
}
}
})
}])

读书单元:

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
}

(我在这里使用 localStorage 在客户端存储 sessionId,但是您也可以在 AuthService 中设置它,例如在 $http 调用之后)