问题 如何为express.static模拟http.ServerResponse和http.IncomingMessage


我测试自己的路由处理程序没有问题,但在这种情况下我想测试express的静态处理程序。我无法为我的生活弄清楚为什么它会悬挂。显然,我缺少一些回调或者我需要发出一些事件。

我尽力做出最小的例子。

var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');

function MockResponse(callback) {
  stream.Writable.call(this);
  this.headers = {};
  this.statusCode = -1;
  this.body = undefined;

  this.setHeader = function(key, value) {
    this.headers[key] = value;
  }.bind(this);

  this.on('finish', function() {
    console.log("finished response");
    callback();
  });
};

util.inherits(MockResponse, stream.Writable);

MockResponse.prototype._write = function(chunk, encoding, done) {
  if (this.body === undefined) {
    this.body = "";
  }
  this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
  done();
};

function createRequest(req) {
  var emitter = new events.EventEmitter();
  req.on = emitter.on.bind(emitter);
  req.once = emitter.once.bind(emitter);
  req.addListener = emitter.addListener.bind(emitter);
  req.emit = emitter.emit.bind(emitter);
  return req;
};

describe('test', function() {

  var app;

  before(function() {
    app = express();
    app.use(express.static(__dirname));
  });

  it('gets test.js', function(done) {

    var req = createRequest({
        url: "http://foo.com/test.js",
        method: 'GET',
        headers: {
        },
    });
    var res = new MockResponse(responseDone);
    app(req, res);

    function responseDone() {
      console.log("done");
      done();
    }

  });

});

建立,

mkdir foo
cd foo
mkdir test
cat > test/test.js   # copy and paste code above
^D
npm install express
npm install mocha
node node_modules/mocha/bin/mocha --recursive

它只是超时。

我错过了什么?

我还尝试将请求设为可读流。不用找了

var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');

function MockResponse(callback) {
  stream.Writable.call(this);
  this.headers = {};
  this.statusCode = -1;
  this.body = undefined;

  this.setHeader = function(key, value) {
    this.headers[key] = value;
  }.bind(this);

  this.on('finish', function() {
    console.log("finished response");
    callback();
  });
};

util.inherits(MockResponse, stream.Writable);

MockResponse.prototype._write = function(chunk, encoding, done) {
  if (this.body === undefined) {
    this.body = "";
  }
  this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
  done();
};

function MockMessage(req) {
  stream.Readable.call(this);
  var self = this;
  Object.keys(req).forEach(function(key) {
    self[key] = req[key];
  });
}

util.inherits(MockMessage, stream.Readable);

MockMessage.prototype._read = function() {
  this.push(null);
};


describe('test', function() {

  var app;

  before(function() {
    app = express();
    app.use(express.static(__dirname));
  });

  it('gets test.js', function(done) {

    var req = new MockMessage({
        url: "http://foo.com/test.js",
        method: 'GET',
        headers: {
        },
    });
    var res = new MockResponse(responseDone);
    app(req, res);

    function responseDone() {
      console.log("done");
      done();
    }

  });

});

我一直在挖。查看静态服务器内部我看到它通过调用创建了一个可读流 fs.createReadStream。它确实有效

var s = fs.createReadStream(filename);
s.pipe(res);

所以试着让自己工作得很好

  it('test stream', function(done) {
    var s = fs.createReadStream(__dirname + "/test.js");
    var res = new MockResponse(responseDone);
    s.pipe(res);

    function responseDone() {
      console.log("done");
      done();
    }    
  });

我想也许这是关于表达等待输入流完成的东西,但这似乎也不是。如果我使用响应消费模拟输入流,它就可以正常工作

  it('test msg->res', function(done) {
    var req = new MockMessage({});
    var res = new MockResponse(responseDone);
    req.pipe(res);

    function responseDone() {
      console.log("done");
      done();
    }    
  });

任何有关我可能缺少的东西都会有所帮助

注意:虽然对第三方模拟库的建议表示赞赏,但我仍然真的希望了解自己缺少的东西。即使我最终切换到某个库,我仍然想知道为什么这不起作用。


5771
2017-09-16 01:00


起源

我的猜测就是 before 运行异步。也许尝试使用回调等待它完成? - brandonscript
不是 before 运行异步。那部分完成得很好 - gman


答案:


我发现了两个阻止这个问题的问题 finish 正在执行的回调。

  1. serve-static 使用 send 模块,用于从路径创建文件读取流并将其传递给 res 目的。但该模块使用 on-finished 检查是否的模块 finished 在响应对象中将attribute设置为false,否则为 破坏文件读取流。因此,文件流永远不会有机会发出数据事件。

  2. 表现 初始化 覆盖响应对象原型。所以默认的流方法就像 end() 方法被http响应原型覆盖:

    exports.init = function(app){
      return function expressInit(req, res, next){
        ...
        res.__proto__ = app.response;
        ..
      };
    };
    

    为了防止这种情况,我在静态中间件之前添加了另一个中间件来重置它 MockResponse 原型:

    app.use(function(req, res, next){
      res.__proto__ = MockResponse.prototype; //change it back to MockResponse prototype
      next();
    });
    

以下是为使其适用而进行的更改 MockResponse

...
function MockResponse(callback) {
  ...
  this.finished = false; // so `on-finished` module doesn't emit finish event prematurely

  //required because of 'send' module
  this.getHeader = function(key) {
    return this.headers[key];
  }.bind(this);
  ...
};

...
describe('test', function() {

  var app;

  before(function() {
    app = express();

    //another middleware to reset the res object
    app.use(function(req, res, next){
      res.__proto__ = MockResponse.prototype;
      next();
    });

    app.use(express.static(__dirname));
  });

  ...

});

编辑:

正如@gman指出的那样,可以使用直接属性而不是原型方法。在这种情况下,不需要覆盖原型的额外中间件:

function MockResponse(callback) {
  ...
  this.finished = false; // so `on-finished` module doesn't emit finish event prematurely

  //required because of 'send' module
  this.getHeader = function(key) {
     return this.headers[key];
  }.bind(this);

  ...

  //using direct property for _write, write, end - since all these are changed when prototype is changed
  this._write = function(chunk, encoding, done) {
    if (this.body === undefined) {
      this.body = "";
    }
    this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
    done();
  };

  this.write = stream.Writable.prototype.write;
  this.end = stream.Writable.prototype.end;

};

10
2017-09-20 04:05



谢谢。我仍然遇到了各种各样的问题,我还没有完全理解,但至少我有一些工作要做。作为我改变'write_'的一个例子做一个直接属性然后我不应该关心express的原型更改,但是因为其他地方表达的期望,该对象确实是从OutgoingMessage继承的ServerResponse。当你把原型放回去时,为什么它会起作用呢? - gman
是的,直接财产应该有效。怎么样的流的其他方法,如 end ?你做了直接财产吗?因为,当我测试时,我确实遇到了错误 end 方法没有 _write。 - hassansin
@gman,为直接属性添加了代码更改。 - hassansin


看来我的答案并不完整。由于某种原因,应用程序仅在找不到文件时才起作用。首先要调试的是在shell(或cmd)中执行以下操作:

export DEBUG=express:router,send

然后运行测试,你会得到更多的信息。

与此同时,我仍然在研究这个问题,现在,请忽略我的答案。

-----------忽略这一点,直到我确认它确实有效-----------

似乎表达静态并不支持你给它的绝对路径(__dirname)。

尝试:

app.use(express.static('.'));

它会起作用。请注意,您当前的mocha运动员目录是'test /'

我不得不承认这是一个相当神秘的问题。我通过这样做尝试'填补'它:

app.use(express.static(__dirname + '/../test')

但它仍然无效。即使指定完整路径也没有解决这个问题。奇怪。


3
2017-09-19 13:56



我发现大多数模块都有调试选项。我建议你在你的node_modules目录中搜索require('debug')并打开所有调试选项。它可能会让您深入了解发生了什么。我注意到,明确提到它为开发设置环境,它可能与它有关。我使用的调试标志列表是:DEBUG = express:router,send,finalhandler,express:application,express:router:layer,mocha:runnable,express:router:route,mocha:runnable,mocha:runner,express:view最后一点,您可以进入node_modules目录并添加文件调试消息 - Meir
此外,静态使用名为send的模块。如果你研究一下,你会发现它有一个关于它是多么混乱以及它需要重构的评论。 - Meir