我试图通过使用tcp包编码来证明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控制台),有时数据事件将被触发两次,最终导致错误,因为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请求很好,但第三次请求被分成两个不同的部分。请求的标题将在一个数据事件中,而正文在另一个数据事件中。
我与少数开发人员讨论过这个问题,我们猜测它可能与我的操作系统的TCP堆栈有关,如果这可能很重要,那就是OSX Sierra。
我没有看到任何其他方法来修补它而不是将缓冲区累积到在较高范围内声明的变量,然后使用丑陋的计时器技巧,最终类似于< / em>可取消的setImmediate。
var timer = false, data = '';
socket.on('data', buffer => {
data += buffer.toString();
clearTimeout(timer);
timer = setTimeout(() => process(socket, data), 1)
})
问题很简单:我知道这个修复在很多方面都是非常错误的,但是在没有使用流或http包的情况下我看不到其他修复。你能让我高兴吗?
答案 0 :(得分:2)
这就是TCP的工作原理。 TCP是一个字节流。应用层上没有包含边界(甚至请求)的数据包。在连接的一端写入n字节的调用可能导致另一端的n个1字节读取调用。您必须准备好每次读取产生任意数量的字节(最多为读取调用的缓冲区大小 - 但是当您在node.js中获取数据时,您无法影响它)。如果您在应用程序级别需要数据包,则需要自己处理,例如:通过将长度为前缀的数据包写入流中。
然而,HTTP不需要数据包的概念,因为它已经由HTTP协议定义,其中标题和正文结束。