Node.js:分块传输编码

16

那段代码是否符合HTTP/1.1规范?

var fs = require('fs')
var http = require('http')

var buf=function(res,fd,i,s,buffer){
 if(i+buffer.length<s){
  fs.read(fd,buffer,0,buffer.length,i,function(e,l,b){
   res.write(b.slice(0,l))
   //console.log(b.toString('utf8',0,l))
   i=i+buffer.length
   buf(res,fd,i,s,buffer)
  })
 }
 else{
  fs.read(fd,buffer,0,buffer.length,i,function(e,l,b){
   res.end(b.slice(0,l))
   fs.close(fd)
  })
 }
}

var app = function(req,res){
 var head={'Content-Type':'text/html; charset=UTF-8'}
 switch(req.url.slice(-3)){
  case '.js':head={'Content-Type':'text/javascript'};break;
  case 'css':head={'Content-Type':'text/css'};break;
  case 'png':head={'Content-Type':'image/png'};break;
  case 'ico':head={'Content-Type':'image/x-icon'};break;
  case 'ogg':head={'Content-Type':'audio/ogg'};break;
  case 'ebm':head={'Content-Type':'video/webm'};break;
 }
 head['Transfer-Encoding']='chunked'
 res.writeHead(200,head)
 fs.open('.'+req.url,'r',function(err,fd){
  fs.fstat(fd,function(err, stats){
   console.log('.'+req.url+' '+stats.size+' '+head['Content-Type']+' '+head['Transfer-Encoding'])
   var buffer = new Buffer(100)
   buf(res,fd,0,stats.size,buffer)
  })
 })
}

http.createServer(app).listen(8000,"127.0.0.1")
console.log('GET http://127.0.0.1:8000/appwsgi/www/index.htm')

我认为我在此违反了HTTP/1.1规范?文本文件似乎都能正常工作,但这可能只是巧合。我的响应头是“200 OK”,还是需要改成“100”?一个响应头是否足够?

3个回答

15

由于Node.js隐式设置 'Transfer-Encoding: chunked',因此我只需要在标头中发送带有字符集的内容类型,如:

'Content-Type': 'text/html; charset=UTF-8'

最初它是:

'Content-Type': 'text/html'

...它并没有起作用。立即指定"charset=UTF-8"可以强制Chrome渲染分块响应。


2
使用明确的字符集,我的代码可以正常工作,但我注意到它在 text/plain 内容类型下无法工作(在 Chrome 上测试)... 行为很奇怪... - Francesco Casula
我不太确定为什么会出现这种行为。我会测试一下,稍后回复。 - esengineer
在我的测试中,它在Firefox上可以工作,但在Chrome上不行(仅适用于text/plain内容类型)... - Francesco Casula
3
@fra_casula 我也遇到了同样的问题。这似乎是WebKit中的一个bug:https://code.google.com/p/chromium/issues/detail?id=156023 有一个解决方法是添加“X-Content-Type-Options: nosniff”头部。 - Dmitri Shuralyov
比较老的问题,但是有人知道,“end”事件是否在“error”事件之后触发吗? - decades
也许这些年来有所改变,但添加charset=UTF-8并不能使Chrome显示内容随时到达。 - Brian White

14

如果您正在进行分块传输编码,实际上需要设置以下标头:

Transfer-Encoding: chunked

您可以从谷歌返回的头文件中看到,它对主页和可能其他页面使用了分块传输:

HTTP/1.1 200 OK
Date: Sat, 04 Jun 2011 00:04:08 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Set-Cookie: PREF=ID=f9c65f4927515ce7:FF=0:TM=1307145848:LM=1307145848:S=fB58RFtpI5YeXdU9; expires=Mon, 03-Jun-2013 00:04:08 GMT; path=/; domain=.google.com
Set-Cookie: NID=47=UiPfl5ew2vCEte9JyBRkrFk4EhRQqy4dRuzG5Y-xeE---Q8AVvPDQq46GYbCy9VnOA8n7vxR8ETEAxKCh-b58r7elfURfiskmrOCgU706msiUx8L9qBpw-3OTPsY-6tl; expires=Sun, 04-Dec-2011 00:04:08 GMT; path=/; domain=.google.com; HttpOnly
Server: gws
X-XSS-Protection: 1; mode=block
Transfer-Encoding: chunked

编辑 哎呀,那篇读起来太复杂了:

var app = function(req,res){
 var head={'Content-Type':'text/html'}
 switch(req.url.slice(-3)){
  case '.js':head={'Content-Type':'text/javascript'};break;
  case 'css':head={'Content-Type':'text/css'};break;
  case 'png':head={'Content-Type':'image/png'};break;
  case 'ico':head={'Content-Type':'image/x-icon'};break;
  case 'ogg':head={'Content-Type':'audio/ogg'};break;
  case 'ebm':head={'Content-Type':'video/webm'};break;
 }
 res.writeHead(200,head)
 var file_stream = fs.createReadStream('.'+req.url);
 file_stream.on("error", function(exception) {
   console.error("Error reading file: ", exception);
 });
 file_stream.on("data", function(data) {
   res.write(data);
 });
 file_stream.on("close", function() {
   res.end();
 });
}

这是一个不错的流式缓冲区,可以供你写入。 我写了一篇关于不同文件读取方式的博客文章,建议你查看以便更好地在node的异步环境中处理文件。


这很令人困惑,因为当发送分块包时,响应是否需要是200呢? - Gert Cuykens
@Gert,请阅读我在回复中发布的来自Google的标题,其中包含200 OK。当然,如果您正在重定向或执行其他类型的操作而不是“是的,我现在就有这个文件,并将发送给您”的情况,则应使用适当的标题。我建议您查看所有标题及其含义:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html。如果您想继续像这样在低级别上使用node.js,则这将对您有益。 - onteria_

4
为什么你要手动执行所有的fs操作?使用fs.createReadStream()函数可能会更好。
另外,我猜Chrome希望你返回206响应代码。检查req.headers.range,并查看Chrome是否期望返回媒体文件的“范围”。如果是这样,那么你只需要发送Web浏览器请求的文件部分即可。
但为什么要重复造轮子呢?有许多Node模块可以为您完成此类工作。尝试一下Connect/Express的静态中间件。祝你好运!

1
修复代码后,它仅播放媒体一次。我想你说的范围回应是正确的。 - Gert Cuykens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接