15

我的应用中有几项服务指向不同的 API URL。现在我需要为这些服务中的每一个设置不同的标头。我现在的问题是关于 Angular 4 中的新拦截器。是否有可能为特定服务设置一个拦截器?那么每个服务都有其特定的拦截器吗?

希望你们能得到我的问题。

4

4 回答 4

14

TL:DR 答案:

不,没有办法。拦截器旨在拦截所有请求。

长答案:

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

我更喜欢这里提供的解决方案: Example 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);
}

这是一个很好的例子,但要注意它违反了单一责任原则(设置多个标题)。

除此之外,我的观点是,拦截器对于您的问题来说是错误的模式。此外,我没有将拦截器视为向请求添加不记名令牌的解决方案。那是我的用例,它把我带到了这里。

基本上我会挑战你的架构并重新考虑你如何创建请求。此问题的解决方案可能是以下设计:

摘要库

具有 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 装饰器

示例实现

于 2018-05-31T13:51:20.337 回答
2

实际上有一种方法可以为特定服务提供拦截器。注意重点,因为它并不是真正的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 上为我工作。
我希望这有助于其他迷失的灵魂寻找解决方案。

于 2020-09-07T11:58:21.407 回答
1

一旦使用新的 HttpClient 触发请求/响应,所有拦截器都将被调用。您可以做的一件事是标记您的请求,因此您在旨在处理该请求的拦截器中设置正确的标头。

于 2017-09-09T12:42:24.363 回答
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);
  }
于 2020-01-19T14:35:55.250 回答