【问题标题】:How to access final GraphQL-Reponse in nest.js with interceptor如何使用拦截器访问nestjs中的最终GraphQL-Response
【发布时间】:2019-11-17 19:31:52
【问题描述】:

我已经实现了一个LoggingInterceptor,它应该能够访问最终的 GraphQL-Response 及其数据和错误属性 + 原始请求正文和经过身份验证的用户,之前已由 AuthGuard 添加到请求中.(编辑:由@jay-mcdoniel 部分解决:userbody 可通过GqlExecutionContext.create(context).getContext() 访问)

实际上,Interceptor 只提供了一个完全解析的 GraphQL-Object。

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(tap(
      (allData) => console.log(allData),
      (error)=> console.log(error)));
  }
}

这是我的拦截器级。它只是调用 RxJS-Operator tap 来记录 observable 的当前值。

如果我运行以下 GraphQL-Request...

mutation {
  login(data: { username: "admin", password: "123456" }) {
    id
    username
    token
  }
}

...我的服务器使用以下响应正文正确回答:

{
  "data": {
    "login": {
      "id": "6f40be3b-cda9-4e6d-97ce-ced3787e9974",
      "username": "admin",
      "token": "someToken"
    }
  }
}

但是我的拦截器记录到控制台的allData的内容如下:

{
  id: '6f40be3b-cda9-4e6d-97ce-ced3787e9974',
  isAdmin: true,
  username: 'admin',
  firstname: null,
  lastname: null,
  email: null,
  created: 2019-07-05T15:11:31.606Z,
  token: 'someToken'
}

相反,我想查看真实响应体的信息。

我还尝试通过context.switchToHttp().getResponse() 访问 HttpResponse。但这仅包含突变登录方法的参数:

{
  data: [Object: null prototype] { username: 'admin', password: '123456' }
}

编辑

console.log(GqlExecutionContext.create(context).getContext()); 打印(仍然没有 GraphQL-ResponseBody):

{
  headers: {
    /*...*/
  },
  user: /*...*/,
  body: {
    operationName: null,
    variables: {},
    query: 'mutation {\n  login(data: {username: "admin", password: ' +
      '"123456"}) {\n    token\n    id\n    username\n    isAdmin\n  }\n' +
      '}\n'
  },
  res: ServerResponse {
    _events: [Object: null prototype] { finish: [Function: bound resOnFinish] },
    _eventsCount: 1,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: true,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: false,
    _headerSent: false,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      readable: true,
      _events: [Object],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: true,
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      timeout: 120000,
      parser: [HTTPParser],
      on: [Function: socketOnWrap],
      _paused: false,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 566,
      [Symbol(kHandle)]: [TCP],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        /*...*/
      },
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    connection: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      readable: true,
      _events: [Object],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: true,
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      timeout: 120000,
      parser: [HTTPParser],
      on: [Function: socketOnWrap],
      _paused: false,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 566,
      [Symbol(kHandle)]: [TCP],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: 120000,
        _idlePrev: [TimersList],
        _idleNext: [TimersList],
        _idleStart: 3273,
        _onTimeout: [Function: bound ],
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: false,
        [Symbol(refed)]: false,
        [Symbol(asyncId)]: 567,
        [Symbol(triggerId)]: 566
      },
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    _header: null,
    _onPendingData: [Function: bound updateOutgoingData],
    _sent100: false,
    _expect_continue: false,
    req: IncomingMessage {
      /*...*/
    },
    locals: [Object: null prototype] {},
    [Symbol(isCorked)]: false,
    [Symbol(outHeadersKey)]: [Object: null prototype] {
      'x-powered-by': [Array],
      'access-control-allow-origin': [Array]
    }
  },
  _extensionStack: GraphQLExtensionStack { extensions: [ [CacheControlExtension] ] }
}

【问题讨论】:

  • 在 graphql 查询的情况下,我什至无法用它打印任何东西,但是这在常规控制器调用中工作正常。

标签: node.js graphql nestjs


【解决方案1】:

拦截器实际上是在响应之前和之后调用的,或者至少应该是这样,这样就可以有请求前的逻辑(request in)和post-request的逻辑(response out)。您应该能够在调用 next.hanlde() 之前进行所有预请求处理,然后您应该能够在调用 pipe() 之后使用 RxJS Observable operators,例如 tapmap。您的allData 变量应该包含来自请求/响应的所有信息,您甚至可以使用context 变量来获取更多信息。

allData 目前为您打印什么?你试过GqlExecutionContext.create(context).getContext().reqGqlExecutionContext.create(context).getContext().res吗?这些在Guards 文档中被显示用于获取请求和响应对象,就像使用普通 HTTP 调用一样。

【讨论】:

  • 谢谢! allData 包含您将在 graphql 响应的 data 属性中找到的请求数据以及由 typeorm 关系解析的所有属性(即使是那些在 graphql 请求中不存在的属性)。这就是为什么我称它为allData。它不是部分对象。 GqlExecutionContext.create(context).getContext() 用于阅读 context.body.query context.user。但是仍然无法访问graphqlResponse,因为它不是上下文的一部分。
  • 我在我的问题中添加了更多示例并删除了不必要的信息。
  • 老实说,我认为您正在寻找的响应体是 Nest 在引擎盖下处理的东西,并且在没有真正深入了解内部的情况下不会完全暴露给我们。我正在尝试通读源代码,但我不能保证会从中得到很多结果
  • 我完全忘记发GqlExecutionContext.create(context).getContext()的日志了。我已经用这些信息更新了我的问题。
  • @AmmarAhmed 您是否正确设置了context 以在您的GraphqlModule.forRoot/Async 中设置reqres
【解决方案2】:

这就是我在日志拦截器中的做法

    @Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor(private readonly logger: Logger) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // default REST Api
    if (context.getType() === 'http') {
     ...
     ...
    }

    // Graphql
    if (context.getType<GqlContextType>() === 'graphql') {
      const gqlContext = GqlExecutionContext.create(context);
      const info = gqlContext.getInfo();
      const res: Response = gqlContext.getContext().res;
      // Get user that sent request
      const userId = context.getArgByIndex(2).req.user.userId;
      const parentType = info.parentType.name;
      const fieldName = info.fieldName;
      const body = info.fieldNodes[0]?.loc?.source?.body;
      const message = `GraphQL - ${parentType} - ${fieldName}`;

      // Add request ID,so it can be tracked with response
      const requestId = uuidv4();
      // Put to header, so can attach it to response as well
      res.set('requestId', requestId);

      const trace = {
        userId,
        body
      };

      this.logger.info(`requestId: ${requestId}`, {
        context: message,
        trace
      });
      
      return next.handle().pipe(
        tap({
          next: (val: unknown): void => {
            this.logNext(val, context);
          }
        })
      );
    }
    return next.handle();
  }

  /**
   * Method to log response message
   */
  private logNext(body: unknown, context: ExecutionContext): void {
    // default REST Api
    if (context.getType() === 'http') {
      ...
      ...
    }

    if (context.getType<GqlContextType>() === 'graphql') {
      const gqlContext = GqlExecutionContext.create(context);
      const info = gqlContext.getInfo();
      const parentType = info.parentType.name;
      const fieldName = info.fieldName;
      const res: Response = gqlContext.getContext().res;
      const message = `GraphQL - ${parentType} - ${fieldName}`;

      // Remove secure fields from request body and headers
      const secureBody = secureReqBody(body);

      const requestId = res.getHeader('requestId');

      // Log trace message
      const trace = {
        body: { ...secureBody }
      };
      this.logger.info(`requestId: ${requestId}`, {
        context: message,
        trace
      });
    }
  }
}

【讨论】:

    猜你喜欢
    • 2021-04-10
    • 2021-07-27
    • 1970-01-01
    • 2020-02-11
    • 2020-01-03
    • 2020-12-12
    • 2020-12-16
    • 2021-08-08
    • 2021-11-22
    相关资源
    最近更新 更多