繁体   English   中英

如何使用拦截器访问 nest.js 中的最终 GraphQL-Reponse

[英]How to access final GraphQL-Reponse in nest.js with interceptor

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

事实上,拦截器只是提供了一个完全解析的 GraphQL 对象。

@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 。 但这仅包含mutation-login-method的参数:

{
  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] ] }
}

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

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

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

    @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
      });
    }
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM