最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

nestjs中间件GET请求响应体

运维笔记admin13浏览0评论

nestjs中间件GET请求/响应体

nestjs中间件GET请求/响应体

我与nestjs的一个项目工作,并希望记录尽可能多的信息成为可能,这样的一个事情是每一个HTTP请求的响应和请求的主体。我为这一目标的窝中间件:

import {token} from 'gen-uid';
import { inspect } from 'util';
import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
import { Stream } from 'stream';
import { createWriteStream, existsSync, mkdirSync } from 'fs';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
    logfileStream: Stream;

    constructor() {
        if (!existsSync('./logs')) mkdirSync('./logs');
        this.logfileStream = createWriteStream("./logs/serviceName-"+ new Date().toISOString() + ".log", {flags:'a'});
    }

resolve(...args: any[]): MiddlewareFunction {
    return (req, res, next) => {
        let reqToken = token();
        let startTime = new Date();
        let logreq = {
            "@timestamp": startTime.toISOString(),
            "@Id": reqToken,
            query: req.query,
            params: req.params,
            url: req.url,
            fullUrl: req.originalUrl,
            method: req.method,
            headers: req.headers,
            _parsedUrl: req._parsedUrl,
        }

        console.log(
            "timestamp: " + logreq["@timestamp"] + "\t" + 
            "request id: " + logreq["@Id"] + "\t" + 
            "method:  " + req.method + "\t" +
            "URL: " + req.originalUrl);

        this.logfileStream.write(JSON.stringify(logreq));

        const cleanup = () => {
            res.removeListener('finish', logFn)
            res.removeListener('close', abortFn)
            res.removeListener('error', errorFn)
        }

        const logFn = () => {
            let endTime = new Date();
            cleanup()
            let logres = {
                "@timestamp": endTime.toISOString(),
                "@Id": reqToken,
                "queryTime": endTime.valueOf() - startTime.valueOf(),
            }
            console.log(inspect(res));
        }

        const abortFn = () => {
            cleanup()
            console.warn('Request aborted by the client')
        }

        const errorFn = err => {
            cleanup()
            console.error(`Request pipeline error: ${err}`)
        }

        res.on('finish', logFn) // successful pipeline (regardless of its response)
        res.on('close', abortFn) // aborted pipeline
        res.on('error', errorFn) // pipeline internal error

        next();
    };
}
}

然后,我设置此中间件作为一个全球性的中间件来记录所有的请求,但是在看资源和REQ对象,他们都不具备的属性。

在代码示例我设置响应对象打印,运行在我的项目,它返回一个Hello World端点{“消息”:“Hello World”的}我得到以下的输出:

时间戳:2019-01-09T00:37:00.912Z请求ID:2852f925f987方法:GET URL:/你好世界

ServerResponse {域:空,_events:{表面处理:[功能:结合resOnFinish]},_eventsCount:1,_maxListeners:未定义,输出:[],outputEncodings:[],outputCallbacks:[],outputSize:0,可写:真, _Last:假的,升级:假的,chunkedEncoding:假的,shouldKeepAlive:真,useChunkedEncodingByDefault:真,sendDate:真,_removedConnection:假的,_removedContLen:真,_removedTE:真,_contentLength:0,_hasBody:假的,_trailer: '',成品:真,_headerSent:真,插座:空,连接:空,_header:“HTTP / 1.1 304未修改\ r \ NX供电-通过:快递\ r \ nETag:W / “19 c6Hfa5VVP + Ghysj + 6y9cPi5QQbk” \ r \ nDate:星期三,2019年1月9日0点37分00秒GMT \ r \ nConnection:保活\ r \ n \ r \ n”,_onPendingData:[功能:结合updateOutgoingData],_sent100:假的,_expect_continue:假,REQ:IncomingMessage {_readableState:ReadableState {objectMode:假,highWaterMark:16384,缓冲液:本发明的课题,长度:0,管道:空,pipesCount:0,流:真,结束:真,endEmitted:假,内容如下:假,同步:真,needReadable:假,emittedReadable:真,readableListening:假,resumeScheduled:真,破坏:假,defaultEncoding: 'UTF8',awaitDrain:0,readingMore:真,解码器:无效,编码:空},可读:真,域:空,_events:{},_eventsCount:0,_maxListeners:未定义,插座:插座{连接:假,_hadError:假,_handle:本发明的课题,_parent:空,_host:空,_readableState :本发明的课题,可读:真,域:空,_events:本发明的课题,_eventsCount:10,_maxListeners:未定义,_writableState:本发明的课题,可写:真,allowHalfOpen:真,_bytesDispatched:155,_sockname:空,_pendingData :空,_pendingEncoding: '',服务器:本发明的课题,_SERVER:本发明的课题,_idleTimeout:5000,_idleNext:本发明的课题,_idlePrev:本发明的课题,_idleStart:12562,_destroyed:假,解析器:[对象]上:[功能:socketOnWrap],_paused:假的,读作:[功能],_consuming:真,_httpMessage:空,[符号(asyncId)]:151,[符号(bytesRead)]:0,[符号(ASY NCID)]:153,[符号(triggerAsyncId)]:151},连接:插座{连接:假,_hadError:假,_handle:本发明的课题,_parent:空,_host:空,_readableState:本发明的课题,可读:真,域:空,_events:本发明的课题,_eventsCount:10,_maxListeners:未定义,_writableState:本发明的课题,可写:真,allowHalfOpen:真,_bytesDispatched:155,_sockname:空,_pendingData:空,_pendingEncoding: '' ,服务器:本发明的课题,_SERVER:本发明的课题,_idleTimeout:5000,_idleNext:本发明的课题,_idlePrev:本发明的课题,_idleStart:12562,_destroyed:假,解析器:本发明的课题,关于:[功能:socketOnWrap] ,_paused:假的,读作:[功能],_consuming:真,_httpMessage:空,[符号(asyncId)]:151,[符号(bytesRead)]:0,[符号(asyncId)]:153,[符号(triggerAsyncId )]:151},httpVersionMajor:1,httpVersionMinor:1,httpVersion: '1.1',完成:真,标头:{主机: '本地主机:5500', '用户代理':“的Mozilla / 5.0(X11; Ubuntu的; Linux的x86_64的; RV:64.0)的Gecko / 20100101火狐/ 64.0' ,接受: 'text / html的,是application / xhtml + xml的,应用/ XML; Q = 0.9,/ Q = 0.8', '接受语言':“EN-US带连接; q = 0.5' , '接受编码': 'gzip的,放气',连接: '保持活动', '升级不安全-请求': '1', '如果 - 无匹配':“W / “19 c6Hfa5VVP + Ghysj + 6y9cPi5QQbk” '},rawHeaders:[ '主机', '本地主机:5500', '用户代理',' 的Mozilla / 5.0(X11; Ubuntu的版本; Linux x86_64的; RV:64.0)壁虎/ 20100101火狐/ 64.0' , '接受', 'text / html的,是application / xhtml + xml的,应用/ XML; q = 0.9,/ q = 0.8', '接受语言',“EN-US,EN; q = 0.5' , '接受编码', 'gzip的,放气', '连接', '保持活动', '升级不安全-请求', '1', '如果-无 - 匹配',“W /” 19 c6Hfa5VVP + Ghysj + 6y9cPi5QQbk“”],拖车:{},rawTrailers:[],升级:假,网址: '/你好世界',方法: 'GET',的StatusCode:空,statusMessage:空,客户端:插座连接{:假,_hadError:假,_handle:本发明的课题,_parent:空,_host:空,_readableState:本发明的课题,可读:真,域:空,_events:[对象] _eventsCount:10,_maxListeners:未定义,_writableState:本发明的课题,可写:真,allowHalfOpen:真,_bytesDispatched:155,_sockname:空,_pendingData:空,_pendingEncoding: '',服务器:本发明的课题,_SERVER:[对象] ,_idleTimeout:5000,_idleNext:本发明的课题,_idlePrev:本发明的课题,_idleStart:12562,_destroyed:假,解析器:本发明的课题,关于:[功能:socketOnWrap],_paused:假的,读作:[功能],_consuming :真,_httpMessage:空,[符号(asyncId)]:151,[符号(bytesRead)]:0,[符号(asyncId)]:153,[符号(triggerAsyncId)]:151},_consuming:假,_dumped:真,下一个:[功能:下一个],的baseUrl: '',originalUrl: '/你好世界',_parsedUrl:URL {协议:空,斜线:空,AUTH:空,主机:空,端口:空,主机名:空,散:为空,搜索:空,查询:空,路径:“/你好世界”,路径为:“/你好世界”,HREF:“/你好世界”,_raw:“/你好世界”} ,则params:{},查询:{},RES:[循环],体:{},路线:路线{路径: '/你好世界',堆栈:[阵列],方法:〔O bject]}},当地人:{},的StatusCode:304,statusMessage: '未修改',[符号(outHeadersKey)]:{ '×供电-由':[ 'X供电-通过', '快'] ,ETAG:[ 'ETag的', 'W / “19 c6Hfa5VVP + Ghysj + 6y9cPi5QQbk”']}}

在响应对象没有地方做的{“消息”:“Hello World”的出现}消息,我想知道如何从资源和REQ对象获得的身体,如果有可能,请。

注:我知道nestjs有Interceptors,但下面的文档是这么说,中间件应该是这个问题的解决方案。

回答如下:

我对这个问题意外地运行,它是在“关联”,以my question上市。

我可以扩展Kim Kern's answer多一点,大约回应。

与响应的问题是,反应体是不响应的对象,但流的属性。为了能够得到它,你需要重写的方法,其中写入该流。

像金克恩已经说了,你可以欣赏到this thread,有公认的答案如何做到这一点。

或者你可以express-mung中间件,这将做到这一点给你,例如:

var mung = require('express-mung');
app.use(mung.json(
  function transform(body, req, res) {
    console.log(body); // or whatever logger you use
    return body;
  }
));

还有其他两种不同的方式,这NestJS可以为您提供:

  • Interceptors,像你说的。还有例如,在文档LoggingInterceptor的。
  • 你可以写控制器的方法,这将拦截他们的反应装饰。
import { isObservable, from, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

/**
 * Logging decorator for controller's methods
 */
export const LogReponse = (): MethodDecorator =>
  (target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {

    // save original method
    const original = descriptor.value;

    // replace original method
    descriptor.value = function() { // must be ordinary function, not arrow function, to have `this` and `arguments`

      // get original result from original method
      const ret = original.apply(this, arguments);

      // if it is null or undefined -> just pass it further
      if (ret == null) {
        return ret;
      }

      // transform result to Observable
      const ret$ = convert(ret);

      // do what you need with response data
      return ret$.pipe(
        map(data => {
          console.log(data); // or whatever logger you use
          return data;
        })
      );
    };

    // return modified method descriptor
    return descriptor;
  };

function convert(value: any) {
  // is this already Observable? -> just get it
  if (isObservable(value)) {
    return value;
  }

  // is this array? -> convert from array
  if (Array.isArray(value)) {
    return from(value);
  }

  // is this Promise-like? -> convert from promise, also convert promise result
  if (typeof value.then === 'function') {
    return from(value).pipe(mergeMap(convert));
  }

  // other? -> create stream from given value
  return of(value);
}

但请注意,这将拦截器之前执行,因为这个装饰改变方法的行为。

而且我不认为这是很好的方法,做记录,刚才提到它各种:)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论