NestJS拦截器:无法在传出请求上设置HTTP标头

问题描述 投票:0回答:2

我正在NestJS中编写具有一组通用标头的API。我决定使用拦截器,以便将标头附加到传出的请求中。标头未附加到请求,因此请求继续失败。

拦截器

import * as utils from '../utils/utils';
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor
} from '@nestjs/common';
import { HEADERS } from '../middlewares/headers.constant';
import { Observable } from 'rxjs';
import { Request } from 'express';
import { DATA_PARTITION_ID } from '../app.constants';

@Injectable()
export class HeadersInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<void> {
    const ctx = context.switchToHttp();
    const request: Request = ctx.getRequest();

    this.setHeaders(request);

    return next.handle();
  }

  private setHeaders(request): void {
    this.updateHeaders(request, HEADERS.ACCEPT, 'application/json');
    this.updateHeaders(request, HEADERS.CONTENT_TYPE, 'application/json');
    this.updateHeaders(request, HEADERS.ACCEPT_ENCODING, 'gzip, deflate, br');
    this.updateHeaders(
      request,
      HEADERS.DATA_PARTITION_ID,
      DATA_PARTITION_ID
    );
    this.updateHeaders(
      request,
      HEADERS.AUTHORIZATION,
      `Bearer ${utils.parseCookies(request).stoken}`
    );
    this.updateHeaders(request, HEADERS.APP_KEY, '');
  }

  private updateHeaders(
    request: Request,
    property: string,
    value: string
  ): void {
    if (!request.headers.hasOwnProperty(property)) {
      request.headers[property] = value;
    } else {
      void 0;
    }
  }
}

此拦截器仅做一件事,访问请求并附加标头,然后将控件传递给下一个处理程序。

枚举

export enum HEADERS {
  DATA_PARTITION_ID = 'Data-Partition-Id',
  AUTHORIZATION = 'Authorization',
  CONTENT_TYPE = 'Content-Type',
  APP_KEY = 'appkey',
  ACCEPT = 'accept',
  ACCEPT_ENCODING = 'accept-encoding'
}

Controller

import { Body, Controller, Post, Req, UseInterceptors } from '@nestjs/common';
import { HeadersInterceptor } from '../interceptors/headers.interceptor';
import { SearchData } from './models/search-data.model';
import { SearchResults } from './models/search-results.model';
import { SearchService } from './search.service';

@Controller('')
@UseInterceptors(new HeadersInterceptor())
export class SearchController {
  constructor(private searchService: SearchService) {}

  @Post('api/search')
  async searchDataById(@Body() searchData: SearchData, @Req() req): Promise<SearchResults> {
    console.log(req.headers);
    return await this.searchService.getSearchResultsById(searchData);
  }
}

服务

import { HttpService, HttpStatus, Injectable } from '@nestjs/common';
import { AppConfigService } from '../app-config/app-config.service';
import { DataMappingPayload } from './models/data-mapping-payload.model';
import { SearchData } from './models/search-data.model';
import { SearchModelMapper } from './search.service.modelmapper';
import { SearchResults } from './models/search-results.model';
import { ServiceException } from '../exception/service.exception';

@Injectable()
export class SearchService {
  constructor(
    private searchModelMapper: SearchModelMapper,
    private configService: AppConfigService,
    private readonly httpService: HttpService
  ) {}

  async getSearchResultsById(searchData: SearchData): Promise<SearchResults> {
    if (searchData.filters.collectionId) {
      console.log(this.configService.appConfig.urls.SEARCH_RESULTS_BY_COLLECTION_ID_URL.replace(
          '${collectionId}',
          searchData.filters.collectionId
        )
      );
      const searchResultsAPI = await this.httpService
        .get(
          this.configService.appConfig.urls.SEARCH_RESULTS_BY_COLLECTION_ID_URL.replace(
            '${collectionId}',
            searchData.filters.collectionId
          )
        )
        .toPromise();
      const kinds = this.searchModelMapper.getUniqueKinds(
        searchResultsAPI.data.results
      );
      const mappingPayload = await this.getDataMapping(kinds);
      return this.searchModelMapper.generateSearchResults(
        kinds,
        mappingPayload,
        searchResultsAPI.data.results
      );
    } else {
      this.raiseException();
    }
  }

  async getDataMapping(kinds: string[]): Promise<[]> {
    const entityKindNames: DataMappingPayload = {
      entityKindNames: kinds
    };
    const dataMappingAPI = await this.httpService
      .post(
        this.configService.appConfig.urls.DATA_CATALOG_SERVICE_URL,
        JSON.stringify(entityKindNames)
      )
      .toPromise();

    return dataMappingAPI.data.entityViewData;
  }

  // To be moved to util functions
  private raiseException(): void {
    throw new ServiceException(
      {
        message: 'This does not have a collection id',
        missing: 'Collection Id',
        code: HttpStatus.BAD_REQUEST
      },
      HttpStatus.BAD_REQUEST
    );
  }
}

当我访问controller中的req.headers时,确实获得了我需要通过拦截器设置的所有标题。

{
[0]   'accept-encoding': 'gzip, deflate, br',
[0]   'accept-language': 'en-US,en;q=0.9',
[0]   cookie: '_ga=GA1.2.1433024000.1564057108; wfx_unq=AL2gejqqEGELJ5FQ; trafficManagerV2Token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1UVTJOakU0T0RJNE1nP
T0ifQ.eyJwZXJtaXRVcmwiOiJodHRwczovL2V2ZC5kYXRhLmRlbGZpLmNsb3VkLnNsYi1kcy5jb20vIiwiY291bnRyeUNvZGUiOiJJTiIsImlzcyI6ImNmcy10cmFmZmljLW1hbmFnZXIiLCJpYXQiOjE1NjYyNzM3ND
gsImV4cCI6MTU2NjI4ODE0OCwiYXVkIjoiaHR0cHM6Ly9ldmQuZGF0YS5kZWxmaS5jbG91ZC5zbGItZHMuY29tLyJ9.QHvZGR4DXdGpsWWNCnypPFttaBlpBCBSvy2N_Z0mgSD6W86g4f61GhO2XzFyIm7P20qAjkXHl
3CIo8R66wtYQqMIAOEd2BPcJVnKg9vdt2kxd1Fhk66BWTFd_xtTdyEgcwMuCmEkYEeFK1_cXrlbeGYpaRiXD6w6K1_2U1Wxtbu82BNp7R4eAuiLRbbLBdsuPgLwXsOI8YpFTMdpiUDMnZnTfw-Fr2F93KMzHKTswLy0y
QZVPtONj8BwXDPf15s2vLiTyzgof4ByM7O_eBIbBDse5tFufBXFABnr709Oi6AKUGMeVKsgwCo1d1Yxs7MR6nbNmyG3rFxKzhk5Xxehzw; x-origin-country=IN; stoken=eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
zI1NiIsImtpZCI6Ik1UVTRORGcxTWpFeU1BPT0ifQ.eyJzdWIiOiJycHJhYmh1N0BzbGIuY29tIiwiaXNzIjoic2F1dGgtcHJldmlldy5zbGIuY29tIiwiYXVkIjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC
5jb20iLCJpYXQiOjE1ODQ5Mzk3MjYsImV4cCI6MTU4NTAyNjEyNiwicHJvdmlkZXIiOiJzbGIuY29tIiwiY2xpZW50IjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJ1c2VyaWQiOiJycHJhYmh1N0
BzbGIuY29tIiwiZW1haWwiOiJycHJhYmh1N0BzbGIuY29tIiwiYXV0aHoiOiIiLCJsYXN0bmFtZSI6IlByYWJodSIsImZpcnN0bmFtZSI6IlJ1c2hpa2VzaCBTdWJoYXNoIiwiY291bnRyeSI6IiIsImNvbXBhbnkiOi
IiLCJqb2J0aXRsZSI6IiIsInN1YmlkIjoiRjBfSUMxSjl4SHBaSGVUbnVBaWRCYVhtdzI1YmxuOUhYSXIwMnNscW8wTSIsImlkcCI6Im8zNjUiLCJoZCI6InNsYi5jb20iLCJkZXNpZCI6InJwcmFiaHU3LXNsYi1jb2
0tNWZkODc5NzZAZGVzaWQuZGVsZmkuc2xiLmNvbSIsImNvbnRhY3RfZW1haWwiOiJycHJhYmh1N0BzbGIuY29tIiwicnRfaGFzaCI6IlAzUG1yRXd5WExCR1VwTi05TTdybEEifQ.Z61iRRoS7J1IpF_V_rWLcrgeaSf
QyZG3K5vU4jps_LqB3VkPSvjHXLdv7Ga_LLPI_v2J-WFityHVBnYxLEzKmOuNc_jToPwmBqCmLLfSzIFGiJrFKby09ZbVoCCLHxjyUwB_Uc2VmWuYLce7oPpVFxelgRqnRjO3ymlPm65OvrR09fHiOlo52TULwbyyzeg
xzfodkl0eVTM7TURDi1RxGNHvw8Ghxt--AVIcgCT7hBDxA6w11D7Cr6fWBp1VpE2yawTESUWtZJn5tBmMZeZq2QobptNcuFdiAstQpvi_B5MqY1HY5LjVLOb2jAnEoCTl_gmEfyWr_aIKAFioK4YcQQ; _gid=GA1.2.
1341318697.1566283218; account-id=tenant1; _gat=1; traffic-manager-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1UVTRORGcxTWpFeU1BPT0ifQ.eyJwZXJtaXRVcmwi
OiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJjb3VudHJ5Q29kZSI6IklOIiwiaXNzIjoiY2ZzLXRyYWZmaWMtbWFuYWdlciIsImlhdCI6MTU4NDkzMTk5NywiZXhwIjoxNTg0OTQ2Mzk3LCJhdWQiOiJodHRwOi8vbG9j
YWxob3N0OjgwODAifQ.uVs2Uuy_Okzn0t3GPESH7cCR4OAb_ISr160JrydaKfkHogaKsuNEa7BI1vgQY8uywYle2P_sRaYT_FaoR9cF2iqHH7R7YHVdKEdNm_Gb2ji8nnLMjXORAMB78YtHt4SvnCNYrAxTqRPVhxRot
dQc6dcrVgzkxKxedDvnZTR81DfoOa00oeKrU7X62MSGMRDmz7TYLNxbaw0viJ-MlJ2AMHs_YhyRSHvmmG_5d0TVfNLBSnAiXlTH06iigVXfT5v-BbRukJJzaW1Pj30fde2G2ni0SZ8sK6nlrpu_0Tlu5-v1dKmdofhBs
qC8y8sCjZ8fTw4yZICl5AwPGZ4IOLkAeg',
[0]   'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
[0]   'content-type': 'application/json',
[0]   accept: 'application/json',
[0]   appkey: '',
[0]   'cache-control': 'no-cache',
[0]   'postman-token': 'cb397012-71aa-460a-b66b-28600538faf9',
[0]   host: 'localhost:8080',
[0]   'content-length': '77',
[0]   connection: 'keep-alive',
[0]   'Data-Partition-Id': 'tenant1',
[0]   Authorization: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1UVTRORGcxTWpFeU1BPT0ifQ.eyJzdWIiOiJycHJhYmh1N0BzbGIuY29tIiwiaXNzIjoic2F1dGgtcHJldmlldy5z
bGIuY29tIiwiYXVkIjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJpYXQiOjE1ODQ5Mzk3MjYsImV4cCI6MTU4NTAyNjEyNiwicHJvdmlkZXIiOiJzbGIuY29tIiwiY2xpZW50IjoidGVzdC1zbGJk
ZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJ1c2VyaWQiOiJycHJhYmh1N0BzbGIuY29tIiwiZW1haWwiOiJycHJhYmh1N0BzbGIuY29tIiwiYXV0aHoiOiIiLCJsYXN0bmFtZSI6IlByYWJodSIsImZpcnN0bmFtZSI6
IlJ1c2hpa2VzaCBTdWJoYXNoIiwiY291bnRyeSI6IiIsImNvbXBhbnkiOiIiLCJqb2J0aXRsZSI6IiIsInN1YmlkIjoiRjBfSUMxSjl4SHBaSGVUbnVBaWRCYVhtdzI1YmxuOUhYSXIwMnNscW8wTSIsImlkcCI6Im8z
NjUiLCJoZCI6InNsYi5jb20iLCJkZXNpZCI6InJwcmFiaHU3LXNsYi1jb20tNWZkODc5NzZAZGVzaWQuZGVsZmkuc2xiLmNvbSIsImNvbnRhY3RfZW1haWwiOiJycHJhYmh1N0BzbGIuY29tIiwicnRfaGFzaCI6IlAz
UG1yRXd5WExCR1VwTi05TTdybEEifQ.Z61iRRoS7J1IpF_V_rWLcrgeaSfQyZG3K5vU4jps_LqB3VkPSvjHXLdv7Ga_LLPI_v2J-WFityHVBnYxLEzKmOuNc_jToPwmBqCmLLfSzIFGiJrFKby09ZbVoCCLHxjyUwB_U
c2VmWuYLce7oPpVFxelgRqnRjO3ymlPm65OvrR09fHiOlo52TULwbyyzegxzfodkl0eVTM7TURDi1RxGNHvw8Ghxt--AVIcgCT7hBDxA6w11D7Cr6fWBp1VpE2yawTESUWtZJn5tBmMZeZq2QobptNcuFdiAstQpvi_B
5MqY1HY5LjVLOb2jAnEoCTl_gmEfyWr_aIKAFioK4YcQQ'
[0] }

当我检查实际请求的日志时,它说Authorizationnull。这意味着该请求不会被拦截,也不会附加标头。

有人遇到过类似的问题吗?

node.js typescript express interceptor nestjs
2个回答
0
投票

如果我对您的理解正确,您希望将标题添加到HttpService的传出HTTP调用中。 NestJS中的interceptor适用于IncomingMessage(通常是传入请求)和ServerResponse(或通常是传出响应)。它看不到从HttpService或任何其他HTTP客户端发送的内容。相反,您需要在方法级别或模块级别(如果它们都是通用值)处设置标头。 HttpModule具有registerregisterAsync方法,可用于将值传递给每个HttpService调用,因此,如果您具有通用标头,则可以在其中管理它们:

@Module({
  imports: [
    HttpModule.register({
      headers: {} // object of headers you want to set
    }),
  ]
})
export class MyModule {}

现在,当您使用httpService.get(url)时,将随其发送标题。


0
投票

如果外部HTTP调用始终需要标头,则可以直接在nestJs Axios Interceptor中添加HttpService,就像他在此post中记录请求的方法一样。

重要的部分是:

  1. 使您的app.module.ts工具OnModuleInit
  2. 添加方法onModuleInit()
  3. onModuleInit()中,添加this.httpService.axiosRef.interceptors.request.use(functionThatWillAddHeadersToRequest(config));

[config包含请求所需要的所有信息,包括标题。

现在所有使用HttpService的传出请求都应具有HTTP标头。

Axios interceptors github

© www.soinside.com 2019 - 2024. All rights reserved.