【发布时间】:2016-11-14 14:46:35
【问题描述】:
我试图通过使用 tcp 包对 http 服务器进行编码来演示 http 服务器如何工作的最简单方式。我之前做过几次,但今天我遇到了一个意想不到的行为,因为来自套接字对象的 data 事件被触发一次或多次随机类似的请求,我想知道为什么,以及如何正确修复它。
请注意,我知道我应该使用流式处理数据的方式,这是我在第二次演示中所做的。重点是每次都增加复杂性,以使演示更容易理解。
这是服务器。如您所见,它简单易行。
const net = require('net')
const response = `HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Foo: Bar
foobar
`
net.createServer(socket => {
socket.on('data', buffer => {
console.log('----- socket data', Date.now())
console.log(buffer.toString())
socket.write(response)
socket.end()
console.log('-----')
})
socket.on('end', () => console.log('----- socket end.'))
socket.on('close', () => console.log('----- socket close.', '\n'))
}).listen(2000)
为了测试我的服务器,我只需打开任何网络浏览器到http://localhost:2000 并获得响应;但是当使用以下有效负载(使用浏览器的 javascript 控制台)时,有时 data 事件会被触发两次,最终导致错误,因为 write/end 进程无法继续第二次。
var xhr = new XMLHttpRequest();
xhr.open("POST", "/");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({ foo: "bar" }));
这是来自服务器的日志快照,如果有帮助的话:
----- socket data 1479133993862
POST / HTTP/1.1
Host: localhost:2000
Connection: keep-alive
Content-Length: 13
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36
Content-Type: application/json
Accept: */*
Referer: http://localhost:2000/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2
{"foo":"bar"}
-----
----- socket end.
----- socket close.
----- socket data 1479133994515
POST / HTTP/1.1
Host: localhost:2000
Connection: keep-alive
Content-Length: 13
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36
Content-Type: application/json
Accept: */*
Referer: http://localhost:2000/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2
{"foo":"bar"}
-----
----- socket end.
----- socket close.
----- socket data 1479133995166
POST / HTTP/1.1
Host: localhost:2000
Connection: keep-alive
Content-Length: 13
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36
Content-Type: application/json
Accept: */*
Referer: http://localhost:2000/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,id;q=0.2,ms;q=0.2,ko;q=0.2
-----
----- socket data 1479133995167
{"foo":"bar"}
events.js:154
throw er; // Unhandled 'error' event
^
Error: write after end
at writeAfterEnd (_stream_writable.js:167:12)
at Socket.Writable.write (_stream_writable.js:212:5)
at Socket.write (net.js:624:40)
at Socket.<anonymous> (/Users/julien/Temp/foo.js:14:12)
at emitOne (events.js:90:13)
at Socket.emit (events.js:182:7)
at readableAddChunk (_stream_readable.js:153:18)
at Socket.Readable.push (_stream_readable.js:111:10)
at TCP.onread (net.js:529:20)
如您所见,前 2 个请求很好,但第 3 个请求被分成 2 个不同的部分。请求的标头将在一个数据事件中,而正文在另一个数据事件中。
我与少数开发人员讨论过这个问题,我们猜测这可能与我的操作系统的 TCP 堆栈有关,如果这很重要的话,那就是 OSX Sierra。
除了将缓冲区累积到在上层范围中声明的变量中之外,我看不到任何其他方法来修补它,然后使用 ugly 计时器技巧,它最终 类似于 一个可取消的 setImmediate。
var timer = false, data = '';
socket.on('data', buffer => {
data += buffer.toString();
clearTimeout(timer);
timer = setTimeout(() => process(socket, data), 1)
})
问题很简单:我知道这个修复在很多方面都非常错误,但是如果不使用流或 http 包,我看不到其他修复。你能启发我吗?
【问题讨论】: