拦截器仅适用于特定服务

18

我的应用程序中有几个服务指向不同的API URL。现在我需要为每个服务设置不同的标头。我的问题是关于Angular 4中新拦截器的。是否有可能为特定服务设置一个拦截器?所以每个服务都有它自己的拦截器?

希望你们理解我的问题。

4个回答

16

TL:DR 答案:

不行,拦截器被设计成拦截所有请求。

长答案:

存储库或请求不应该知道其可以传递的拦截器。因此,我不同意标记请求或检查特定类的解决方案。

我更喜欢这里提供的解决方案:示例 Angular HttpInterceptors

基本上,您的拦截器必须检查服务(中介者模式),以确定是否应添加特定标头。

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log(JSON.stringify(req));

    const token: string = this.currentUserService.token;

    if (token) {
        req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
    }

    if (!req.headers.has('Content-Type')) {
        req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
    }

    req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
    return next.handle(req);
}

这是一个不错的例子,但需要注意它违反了单一职责原则(设置多个头部)。

此外,在我看来,拦截器并不适合解决您的问题。同时,我认为拦截器不是向请求添加 bearer token 的解决方案。这正是我的用例带我来到这里的原因。

基本上,我建议您重新思考如何创建请求的架构。解决此问题的一种方法可能是以下设计:

抽象存储库

具有基本的 get / post / put 等方法,返回 HttpRequest。

具有一个名为“send”的方法,接受 HttpRequest 作为参数。

具体存储库

继承自抽象存储库,并扩展了基本请求功能。

因此,对于您的用例,您只需要一个基本服务,每个特定/自定义服务都继承自此特定服务,从而扩展了请求的行为。

装饰器

为了更进一步扩展这个架构(就像我所做的那样),您可以创建一个 TypeScript 装饰器(在某些情况下不可行,例如需要依赖注入),以扩展所有被装饰函数的行为,例如添加特定头部。这可以是这样的:

import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpRequest, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';

export abstract class BaseRepository<T> {
    constructor(protected client: HttpClient) {

    }

    public createGetRequest(url: string): HttpRequest<T> {
        return new HttpRequest("GET", url);
    }

    public send(request: HttpRequest<T>): Observable<HttpEvent<T>> {
        return this.client.request(request);
    }
}

@Injectable()
export class NormalServiceRepository extends BaseRepository<any> {

    constructor(protected client: HttpClient) {
        super(client);
    }

    public get(url: string): Observable<any> {
        const baseRequest = super.createGetRequest(url);

        baseRequest.headers.set('Content-Type', 'application/json');

        return this.send(baseRequest);
    }
}



@Injectable()
export class AuthServiceRepository extends BaseRepository<any> {

    constructor(protected client: HttpClient) {
        super(client);
    }

    @AcceptsJson()
    @SendsJson()
    @ForwardCredentials()
    public createGetRequest(url: string): HttpRequest<any> {
        return super.createGetRequest(url);
    }

    public get(url: string): Observable<any> {
        const baseRequest = super.createGetRequest(url);
        return this.send(baseRequest);
    }
}

这应该让你对架构的基本外观有一个基本的概念。

更多关于装饰器的内容

TypeScript 装饰器

示例实现


5

实际上,有一种方法可以为特定服务设置拦截器。请注意强调,因为它实际上并不是HttpInterceptor接口的实现。

TL:DR - 跳转到底部的示例。

HttpClient 实际上只是通过所有可能的方法将输入转换为一个称为 HttpRequest 的单一结构,然后将其传递给一个 HttpHandler 实现。正如其名称所示,该实现负责处理 HTTP 请求。
HttpHandler 的实现中,你会找到例如 HttpInterceptingHandler(运行拦截器链)或 HttpXhrBackend(通过 HttpBackend 类继承,此处理程序是最后一个,因为它实际上发送 XHR 请求)。如果你查看 前者类的源代码, 你会发现它实际上依赖于后者(间接地,在 HttpBackend 标记下,默认提供了 HttpXhrBackend 作为实现)。因此,就像拦截器一样,Angular 通过 DI 链接这些处理程序,其中最后一个是执行请求的后端。

你可以做的是将另一个处理程序添加到这个处理程序链中,最好作为第一个处理程序。为此,你需要定义一个类扩展HttpHandler,注入第一个实现HttpHandler(我指的是在HttpHandler标记下提供的实现),然后在完成之后,在handle方法中调用传递。

最后,由于HTTP客户端是Angular服务,可能会在多个组件、指令、服务等之间共享,因此你希望创建自己的实例以避免这种情况,并保持拦截器的隔离性。HttpClient构造函数需要一个HttpHandler实例,你在其中传递自己的实现。

查看我处理身份验证的简单示例:

@Injectable()
export class AuthHandler extends HttpHandler {
    constructor(
        private readonly auth: AuthService,  // custom service providing the auth token
        private readonly next: HttpHandler   // injects the "default" handler -> HttpInterceptingHandler
    ) {
        super();
    }

    /** @override */ handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
        // do whatever you need on the request
        // because of immutability cloning is required
        const clone = req.clone({
            setHeaders: { 'Authorization': 'Bearer ' + auth.getToken() }
        });
        // pass the request to the next handler, eventually ending up in HttpXhrBackend
        return this.next.handle(clone);
    }
}

以及在那个特定服务中处理程序的使用:

export class HeroService {
    protected readonly http: HttpClient;

    constructor(auth: AuthHandler) {
        // create your own instance with you custom handler
        this.http = new HttpClient(auth);
    }

    async getHero(id: string): Hero {
        // every request made through such client goes to the handler for processing
        await this.http.get<Hero>(`/api/heroes/${id}`).toPromise();
    }
}

这段代码在Angular 8上对我有效。
希望这能帮助其他正在寻找解决方案的人。

编辑 2023-06-20:修复了指向GitHub上的Angular代码的链接。


值得一提的是,这也是可测试的,因为只有最后一层(HttpXhrBackend)被替换为一个测试类。我在我的代码中刚刚验证了它。 - inkassso

1

使用新的HttpClient发送请求/响应后,所有拦截器都会被调用。你可以做的一件事是标记你的请求,这样你就可以在专门处理该请求的拦截器中设置正确的头部。


谢谢您的回答。我该如何标记我的请求?感谢您的帮助。 - Tom
例如,您可以创建单独的请求类型类,这些类扩展了HttpRequest,以便您可以按类类型进行过滤。 您可以添加一个特殊的标头进行过滤。 您可以在URL末尾添加哈希标签。 这只是一些例子。 - Daniel Bunte
但是如何识别这些标记请求的响应呢? - Oleksandr Poshtaruk

1
有点晚回答这个问题,但这对我们很有效。是的,它进入了拦截器,但简单的条件可以使其不执行。
 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Do not execute interceptor
    if (some condition)
      return next.handle(req);

    // Else.. do your thing and go next...
    // Add headers, modify reqyest, etc..
    return next.handle(req);
  }

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