Angular HTTP 拦截器

3

Angular http拦截器不能应用于由拦截器内初始化的请求?

在我们的代码库中,每个对API的请求都具有以/api/开头的URL,并且我们有一个拦截器,该拦截器使用我们API的实际地址更新这些请求(使在不同环境下切换更加容易)。 在更新URL后,如果存在访问令牌,则拦截器还会添加Authorization标头。 这一切都非常完美。

但是,有时访问令牌已过期,但我们仍然有一个刷新令牌,在继续进行实际请求之前,我要先向我们的API发出请求以获取新的访问令牌。

因此,在拦截器的request部分中,我们调用负责刷新访问令牌的服务,并在该方法返回的then回调函数中更新原始请求的配置:

//This interceptor only applies to our api calls, do nothing for other requests
if(config.url.substring(0, API_PREFIX.length) !== API_PREFIX) {
    return config;
}

config.url = config.url.replace(API_PREFIX, API_ENDPOINT);

// if we are authenticated, we add the authorization header already
if(AuthSessionService.isAuthenticated()) {
    config.headers['Authorization'] = "Bearer " + AuthSessionService.getAccessToken();
    return config;
}

// so we are not authenticated, but we still do have a refresh-token, this means, we should get a new access-token
if(AuthSessionService.hasRefreshToken()) {
    var deferred = $q.defer(),
        loginService = angular.injector(['MyApp']).get('LoginService');

    loginService.refresh().then(function(result) {
        config.headers['Authorization'] = "Bearer " + AuthSessionService.getAccessToken();
        deferred.resolve(config);
    }).catch(function() {
        deferred.reject('Failed to refresh access token');
    });

    return deferred.promise;
}

//no access-token, no refresh-token, make the call without authorization headers
return config;

然而,登录服务发出的请求似乎没有应用拦截器,因此请求会发送到/api/login而不是实际的API端点。

这是Angular的设计吗?当从拦截器的请求方法中发出新的http请求时,不应用拦截器吗?


你是如何注入 loginService 的?我猜它依赖于 $http,对吗?那么这将会是一个循环依赖的问题。我猜解决方法可能导致了你的问题。 - Sergiu Paraschiv
还有一种反模式是创建自己的 Promise 而不是使用 $http 返回的 Promise。 - charlietfl
我个人会使用广播,这样你就可以从任何地方开始你的新呼叫。 - stackg91
@SergiuParaschiv,loginService是通过angular.injector().get('LoginService')获取的,以避免循环依赖问题。 - schmkr
@charlietfl,之前我是返回了LoginService的promise(也就是$http返回的promise),但是没有成功。所以我尝试过这种方法,但还是没有成功。 - schmkr
1个回答

1

你的代码流程如下:

0) AngularJS 定义了 $httpProvider

1) 你定义了一个依赖于 $httpProviderloginService,以注入 $http

2) 你定义了一个 HTTP 拦截器,它依赖于 loginService 并改变了 $http 的工作方式;

3) 你定义了其他服务,这些服务注入了 $http

看一下 this 函数,它是任何 AngularJS 提供者必须提供的 $get 方法。每次你的服务将 $http 作为依赖项注入时,它都会被调用,并且 返回 $http

现在,如果你回到第396行,你会发现在调用$get时构建了一个reversedInterceptors列表。这是一个局部变量,所以返回的$http实例将能够使用它,而这就是答案:reversedInterceptors对于你注入的每个$http“实例”来说都是不同的引用。

因此,在loginService(1)中注入的$http与注入到所有其他服务中的$http(3)不同,区别在于它的reversedInterceptors还没有包含你在步骤(2)中添加的拦截器。

关于服务和提供者的区别。 服务是基于提供者构建的,基本上做到了这一点:

function MyService() {
    ...
}

angular.service('MyService', MyService);

// which Angular translates to this


function MyServiceProvider() {
    var myService = new MyService();

    this.$get = function() {
        return myService;
    };
}

angular.provider('MyServiceProvider', MyServiceProvider);

当提供者是这样时:

function MyOtherProvider() {
    var someStuff = [1, 2, 3];

    this.$get = function() {
        var otherStuff = [5, 6, 7]; // this is how reversedInterceptors is 
                                    // initialized in `$httpProvider`

        function get(url) {
            // do stuff, maybe use otherStuff
        }

        return {
            get: get
        };
    };
}

angular.provider('MyOtherProvider', MyOtherProvider);

Angular 只会实例化一次 `MyServiceProvider` 和 `MyOtherProvider`,但是它们内部的操作是不同的。

哇,谢谢。但是我认为我对这些服务的理解还不够完整。我以为在任何地方注入服务,都会注入相同的实例。 - schmkr
但它不是一个服务 :) 一个“提供者”不是一个“服务”。这是Angular实例化的东西,具有$get方法,每次注入时Angular都会调用它并给您返回值。正如您所看到的,拦截器列表是在每次调用$get时构建的,因此它取决于调用它的时间。另一方面,Angular服务始终返回相同的实例。 - Sergiu Paraschiv
谢谢。我已经更改了拦截器,将$injector服务注入,并使用该服务获取loginService并返回带有配置拦截器的该服务! - schmkr
是的,这就是为令我最初说“我猜测解决方法是你问题的原因”的原因 :) 注入$injector意味着每次需要时您都可以获得“最新”的$http版本。 - Sergiu Paraschiv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接