初入nodejs,被一个题困惑了,求解答
发布于 10 年前 作者 backsapce 6271 次浏览 最后一次编辑是 8 年前

此题来自nodeschool第二个教学模块Stream Adventure,这个题是第七题HTTP SERVER 题目的要求是把通过浏览器POST进来的内容转成大写直接输出给浏览器(最下面贴有完整题目)

我的实现如下


var tr = through(function(buf){
    this.queue(buf.toString().toUpperCase());
});

var server =  http.createServer(function(req,res){
  if(req.method == 'POST'){
    req.pipe(tr).pipe(res);
  }else{
    res.end('please send a post request');
  }

});
server.listen(Number(process.argv[2]));

但很可惜通不过测试,测试过程如下

ACTUAL EXPECTED


“YET” “YET”
“HOW” “HOW”
“TO” “TO”
“BURGEON.” “BURGEON.”
“IT’S” “IT’S”
“MEANT” !== “MILLIEMS”
“YET” !== “MEANT”
“HOW” !== “OF”
“TO” !== “MILLIEMS”
“BURGEON.” !== “CENTIMENTS”
“IT’S” !== “OF”
“MILLIEMS” !== “DEADLOST”
“MEANT” !== “CENTIMENTS”
“OF” !== “OR”
“MILLIEMS” !== “DEADLOST”
“CENTIMENTS” !== “MISLAID”
“OF” !== “OR”
“DEADLOST” !== “ON”
“CENTIMENTS” !== “MISLAID”
“OR” !== “THEM”
“DEADLOST” !== “ON”
“MISLAID” !== “BUT,”

我发现开始数个测试都通过了,但是到了后面,发现输出的内容比输入慢,每次对应的输出都是之前输入的内容. 这是我比较疑惑的地方,为什么会出现这种情况.

而正确答案的代码是这样的

var http = require('http');
var through = require('through');

var server = http.createServer(function (req, res) {
    if (req.method === 'POST') {
        req.pipe(through(function (buf) {
            this.queue(buf.toString().toUpperCase());
        })).pipe(res);
    }
    else res.end('send me a POST\n');
});
server.listen(parseInt(process.argv[2]));

不知道这两种处理有什么区别,我只是把内建函数换成了变量.

题目的全部内容如下

<a name=“questrue” id=“questrue”>HTTP SERVER</a>

In this challenge, write an http server that uses a through stream to write back the request stream as upper-cased response data for POST requests.

Streams aren’t just for text files and stdin/stdout. Did you know that http request and response objects from node core’s http.createServer() handler are also streams?

For example, we can stream a file to the response object:

var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
    fs.createReadStream('file.txt').pipe(res);
});
server.listen(process.argv[2]);

This is great because our server can response immediately without buffering everything in memory first.

We can also stream a request to populate a file with data:

var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
    if (req.method === 'POST') {
        req.pipe(fs.createWriteStream('post.txt'));
    }
    res.end('beep boop\n');
});
server.listen(process.argv[2]);

You can test this post server with curl:

$ node server.js 8000 &
$ echo hack the planet | curl -d[@-](/user/-) http://localhost:8000
beep boop
$ cat post.txt
hack the planet

Your http server should listen on the port given at process.argv[2] and convert the POST request written to it to upper-case using the same approach as the TRANSFORM example.

As a refresher, here’s an example with the default through callbacks explicitly defined:

var through = require('through')
process.stdin.pipe(through(write, end)).pipe(process.stdout);

function write (buf) { this.queue(buf) }
function end () { this.queue(null)

Do that, but send upper-case data in your http server in response to POST data.

Make sure to npm install through in the directory where your solution file lives.

To verify your program, run: stream-adventure verify program.js.

6 回复

答案和你的代码的唯一区别是,答案对于每一个http请求创建一个through对象,你是用的全局唯一的through对象。

具体原因还不是很清楚-。-

一语点醒了我,答案确实对于每一次的http请求都创建了一个through对象.这也许是造成错误的原因. 我猜测原因是这样的 : 题目中提到了This is great because our server can response immediately without buffering everything in memory first. 这句话说明 through是没有缓存机制的(没有存入内存),如果多个http并发请求共有一个through对象来处理,可能会发生有内容丢失.

我觉得我发现问题了。

你可以自己重新写一份代码,像这样

var http = require('http');
var through = require('through');

var server = http.createServer(function (req, res) {
    if (req.method === 'POST') {
        req.pipe(through(function (buf) {
            this.queue(buf.toString().toLowerCase());
        })).pipe(res);
    }
    else res.end('send me a POST\n');
});
server.listen(parseInt(process.argv[2]));

这份代码和标准答案的唯一区别是输出小写。

然后你可以发现你还是可以通过测试。

然后,通过阅读stream-adventures的代码,我发现问题。

在nodeschool的verify框架中,verify功能作为一个函数实现,这个函数接受两个函数(a和b)和其他参数作为参数,verify首先调用a,b函数分别创建两个流,a是测试流,b是标准答案流。而在每个问题的目录下面会有一个setup.js,这个js在正式调用verify前被调用,将会创建verify所需要的参数,包括a函数和b函数。

在stream-adventures/problems/http_server/setup.js中,创建的参数是这样的:

return {
        a: function (args) { return run(args, aPort) },
        b: function (args) { return run(args, bPort) },
        close: function () { close.forEach(function (f) { f() }) }
};

可以清晰的看到a函数和b函数的定义

而run函数的实现如下:

function run (args, port) {
        var ps = spawn(process.execPath, args.concat(port));
        ps.stderr.pipe(process.stderr);
        close.push(function () { ps.kill() });
        
        var stream = check(aPort);
        if (opts.run) {
            ps.stdout.pipe(process.stdout);
            stream.on('end', function () { ps.kill() });
        }
        return stream;
}

其中check函数,经过我的阅读,实际功能为创建一个输入输出流,向指定端口的服务器发送测试数据,并且将测试结果写入流中待测试。

这其中的bug就很明显了。创建check流的时候,代码为var stream = check(aPort);这就意味着a函数和b函数创建的测试流是同一个流,标准代码根本就没有被测试。在改为var stream = check(port);后,工作正常。

然后回过来看看为什么你的代码(实际是正确的)在这种情况下不能正常工作。

实际多次测试后,我们可以发现LZ所列举的错误情况并不是唯一的,也会出现多次错对交替。在这个模型下,两个客户端几乎同时开始运行,然后开始向服务器发送相同的测试数据。由于你使用全局的trough对象,这个时候a流发送的东西可能流向a流或者b流,b流也是如此,所以说能对能错完全看调度了。。。基本是靠缘分。在使用了标准答案后,由于之前提到的原因,不会造成流混乱,也就可以通过测试。

嗯嗯,好详细的回复啊,非常感谢,我先消化一下您的回复.

@backsapce 主要意思就是,在这道题中,你的代码是正确的,错误的原因在用于测试的代码中

才初学,我得慢慢笑话了,不过setup函数应该有点问题,既然都传了port参数,里面却是用了aport…

回到顶部