流的MP4在Chrome中使用Rails,nginx的和由send_file [英] Streaming mp4 in Chrome with rails, nginx and send_file

查看:504
本文介绍了流的MP4在Chrome中使用Rails,nginx的和由send_file的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不能为我的生活流式MP4与HTML5 &LT Chrome浏览器;视频> 标记。如果我放弃在文件public 那么一切都是肉汁和按预期工作。但是,如果我尝试使用为它服务由send_file ,pretty太多可以想象的一切顺心。我使用的是nginx的代理一个Rails应用程序,与一个具有位置属性,该属性是一个视频模型磁盘上的绝对路径。

I can't for the life of me stream a mp4 to Chrome with a html5 <video> tag. If I drop the file in public then everything is gravy and works as expected. But if I try to serve it using send_file, pretty much everything imaginable goes wrong. I am using a rails app that is proxied by nginx, with a Video model that has a location attribute that is an absolute path on disk.

一开始我尝试:

def show
  send_file Video.find(params[:id]).location
end

和我确信我会在这是现代web开发的荣耀姥。哈。这中都起到Chrome和Firefox,但既不寻求也不有任何想法的视频多久。我戳在响应头,并意识到内容类型正在发送的应用程序/八位字节流并有没有的Content-Length 集。嗯......问心无愧?

And I was sure I would be basking in the glory that is modern web development. Ha. This plays in both Chrome and Firefox, but neither seek and neither have any idea how long the video is. I poked at the response headers and realized that Content-Type is being sent as application/octet-stream and there is no Content-Length set. Umm... wth?

好吧,我想我可以设置这些在轨:

Okay, I guess I can set those in rails:

def show
  video = Video.find(params[:id])
  response.headers['Content-Length'] = File.stat(video.location).size
  send_file(video.location, type: 'video/mp4')
end

此时一切正常pretty多预期在Firefox。它知道该视频是多久,寻求按预期工作。 Chrome似乎知道视频是多久(不显示时间戳,但搜索栏看起来合适的),但求不工作。

At this point everything works pretty much as expected in Firefox. It knows how long the video is and seeking works as expected. Chrome appears to know how long the video is (doesn't show timestamps, but seek bar looks appropriate) but seeking doesn't work.

显然,Chrome是比Firefox挑剔。这要求服务器与价值字节 A 接受-范围头回应,并随后响应请求(这种情况发生时,用户试图)与 206 和文件的相应部分。

Apparently Chrome is pickier than Firefox. It requires that the server respond with a Accept-Ranges header with value bytes and respond to subsequent requests (that happen when the users seeks) with 206 and the appropriate portion of the file.

好吧,所以我借了一些code从这里然后我有这样的:

Okay, so I borrowed some code from here and then I had this:

video = Video.find(params[:id])

file_begin = 0
file_size = File.stat(video.location).size
file_end = file_size - 1

if !request.headers["Range"]
  status_code = :ok
else
  status_code = :partial_content
  match = request.headers['Range'].match(/bytes=(\d+)-(\d*)/)
  if match
    file_begin = match[1]
    file_end = match[2] if match[2] && !match[2].empty?
  end
  response.header["Content-Range"] = "bytes " + file_begin.to_s + "-" + file_end.to_s + "/" + file_size.to_s
end
response.header["Content-Length"] = (file_end.to_i - file_begin.to_i + 1).to_s
response.header["Accept-Ranges"]=  "bytes"
response.header["Content-Transfer-Encoding"] = "binary"
send_file(video.location,
:filename => File.basename(video.location),
:type => 'video/mp4',
:disposition => "inline",
:status => status_code,
:stream =>  'true',
:buffer_size  =>  4096)

现在,Chrome会去追求,但是当你做视频停止播放并永远不会再工作,直到重新加载页面。哎呀。所以我决定玩的卷曲,看看发生了什么事,我发现了这一点:

Now Chrome attempts to seek, but when you do the video stops playing and never works again until the page reloads. Argh. So I decided to play around with curl to see what was happening and I discovered this:

$卷曲--header范围:字节= 200-400的http://本地主机:8080 /录像/ 1 /001.mp4
   ftypisomisomiso2avc1mp41 moovlmvhd @ TRAK \\ TKH

$ curl --header "Range: bytes=200-400" http://localhost:8080/videos/1/001.mp4 ftypisomisomiso2avc1mp41 �moovlmvhd��@��trak\tkh��

$卷曲--header范围:字节= 1200-1400的http://本地主机:8080 /录像/ 1 /001.mp4
   ftypisomisomiso2avc1mp41 moovlmvhd @ TRAK \\ TKH

$ curl --header "Range: bytes=1200-1400" http://localhost:8080/videos/1/001.mp4 ftypisomisomiso2avc1mp41 �moovlmvhd��@��trak\tkh��

不管字节范围请求,该数据总是从文件的开头开始。字节适量被返回(201个字节在这种情况下),但它从文件的开头总是。显然nginx的方面的的Content-Length 头,而忽略内容范围头。

No matter the byte range request, the data always starts from the beginning of the file. The appropriate amount of bytes is returned (201 bytes in this case), but it's always from the beginning of the file. Apparently nginx respects the Content-Length header but ignores the Content-Range header.

我的 nginx.conf 是不变默认值:

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
        worker_connections 768;
}

http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        gzip on;
        gzip_disable "msie6";

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

和我app.conf是pretty基本的:

and my app.conf is pretty basic:

upstream unicorn {
server unix:/tmp/unicorn.app.sock fail_timeout=0;
}

server {
listen 80 default deferred;
root /vagrant/public;
try_files $uri/index.html $uri @unicorn;
location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header HOST $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
}

error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 5;
}

首先我想附带的Ubuntu 14.04 nginx的1.4.x的,那么从PPA试图1.7.x - 相同的结果。我甚至尝试的Apache2和有完全相同的结果。

First I tried the nginx 1.4.x that comes with Ubuntu 14.04, then tried 1.7.x from a ppa - same results. I even tried apache2 and had exactly the same results.

我想重申,视频文件的的问题。如果我将它放在公共然后nginx的用适当的MIME类型,标题和所需要的一切Chrome的服务使其正常工作。

I would like to reiterate that the video file is not the problem. If I drop it in public then nginx serves it with the appropriate mime types, headers and everything needed for Chrome to work properly.

所以我的问题是两个舞伴:

So my question is a two-parter:


  • 为什么不nginx的/阿帕奇处理所有这些东西与自动的由send_file X-阿塞尔 - 重定向 / X-SENDFILE ),如当文件从公共?在轨处理这东西是落伍。

  • Why doesn't nginx/apache handle all this stuff automagically with send_file (X-Accel-Redirect/X-Sendfile) like it does when the file is served statically from public? Handling this stuff in rails is so backwards.

如何赫克可我居然由send_file nginx的(或Apache)使用后,Chrome会很高兴,让求?

How the heck can I actually use send_file with nginx (or apache) so that Chrome will be happy and allow seeking?

更新1

好了,所以我想我会尝试把钢轨的并发症出来的图片,只是看看我能得到的nginx代理的文件正确。所以我打滑了一个死简单nodjs服务器:

Okay, so I thought I'd try to take the complication of rails out of the picture and just see if I could get nginx to proxy the file correctly. So I spun up a dead-simple nodjs server:

var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {
    'X-Accel-Redirect': '/path/to/file.mp4'
});
res.end();
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:3000/');

和铬是心满意足。 = / 卷曲-I 甚至表明,接受-范围:字节内容类型:视频/ MP4 是由nginx的插入自动的 - 因为它的的是。有什么能铁轨做,从这样的preventing nginx的?

And chrome is happy as a clam. =/ curl -I even shows that Accept-Ranges: bytes and Content-Type: video/mp4 is being inserted by nginx automagically - as it should be. What could rails be doing that's preventing nginx from doing this?

更新2

我可能会越来越近......

I might be getting closer...

如果我有:

def show
  video = Video.find(params[:id])
  send_file video.location
end

然后我得到:

$ curl -I localhost:8080/videos/1/001.mp4
HTTP/1.1 200 OK
Server: nginx/1.7.9
Date: Sun, 18 Jan 2015 12:06:38 GMT
Content-Type: application/octet-stream
Connection: keep-alive
Status: 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="001.mp4"
Content-Transfer-Encoding: binary
Cache-Control: private
Set-Cookie: request_method=HEAD; path=/
X-Meta-Request-Version: 0.3.4
X-Request-Id: cd80b6e8-2eaa-4575-8241-d86067527094
X-Runtime: 0.041953

和我有上述所有问题。

但是,如果我有:

def show
  video = Video.find(params[:id])
  response.headers['X-Accel-Redirect'] = video.location
  head :ok
end

然后我得到:

$ curl -I localhost:8080/videos/1/001.mp4
HTTP/1.1 200 OK
Server: nginx/1.7.9
Date: Sun, 18 Jan 2015 12:06:02 GMT                                                                                                                                                                            
Content-Type: text/html                                                                                                                                                                                        
Content-Length: 186884698                                                                                                                                                                                      
Last-Modified: Sun, 18 Jan 2015 03:49:30 GMT                                                                                                                                                                   
Connection: keep-alive                                                                                                                                                                                         
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: request_method=HEAD; path=/
ETag: "54bb2d4a-b23a25a"
Accept-Ranges: bytes

和一切完美的作品。

但是,为什么?那些应该做同样的事情。而为什么不Nginx的设置内容类型自动地在这里像它很简单的例子的NodeJS?我有 config.action_dispatch.x_sendfile_header ='X-加速重定向集。我曾与相同的结果感动来回 application.rb中 development.rb 之间。我想我从来没有提过......这是铁轨4.2.0。

But why? Those should do exactly the same thing. And why doesn't nginx set Content-Type automagically here like it does for the simple nodejs example? I have config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' set. I have moved it back and forth between application.rb and development.rb with the same results. I guess I never mentioned... this is rails 4.2.0.

更新3

现在我已经改变了我的麒麟服务器侦听端口3000(因为我已经变的nginx在3000侦听为例的NodeJS)。现在,我可以直接向麒麟请求(因为它的端口上监听,而不是一个插座),所以我发现,卷曲-I 直接麒麟表明,没有 X-阿塞尔 - 重定向头被发送,只是卷曲 ING麒麟居然直接将文件发送。这就像由send_file 是不是做什么它应该。

Now I've changed my unicorn server to listen on port 3000 (since I already changed nginx to listen on 3000 for the nodejs example). Now I can make requests directly to unicorn (since it's listening on a port and not a socket) so I have found that curl -I directly to unicorn shows that no X-Accel-Redirect header is sent and just curling unicorn directly actually sends the file. It's like send_file isn't doing what it's supposed to.

推荐答案

我的最后的有答案我原来的问题。我没想到我会被关到这里。我所有的研究已经导致死角,哈克非解决方案和它只是工作开箱即用(当然,不是对我来说)。

I finally have the answers to my original questions. I didn't think I'd ever get here. All my research had lead to dead-ends, hacky non-solutions and "it just works out of the box" (well, not for me).

为什么不nginx的/阿帕奇自动地处理所有这些东西与由send_file(X-加速重定向/ X-SENDFILE)就像当文件被从公共静态地提供它呢?在轨处理这东西是落伍。

Why doesn't nginx/apache handle all this stuff automagically with send_file (X-Accel-Redirect/X-Sendfile) like it does when the file is served statically from public? Handling this stuff in rails is so backwards.

他们这样做,但他们必须被正确配置来取悦机架:: SENDFILE(见下文)。试图轨道来处理,这是一个哈克无解。

They do, but they have to be configured properly to please Rack::Sendfile (see below). Trying to handle this in rails is a hacky non-solution.

如何赫克可我居然由send_file nginx的(或Apache)使用后,Chrome会很高兴,让求?

How the heck can I actually use send_file with nginx (or apache) so that Chrome will be happy and allow seeking?

我有足够的绝望,开始围绕机架源$ C ​​$ C戳而这也正是我发现我的答案,的意见机架:: SENDFILE 。它们的结构类似于文档,你可以找到在 ruby​​doc

I got desperate enough to start poking around rack source code and that's where I found my answer, in the comments of Rack::Sendfile. They are structured as documentation that you can find at rubydoc.

无论出于何种原因,机架:: SENDFILE 要求前端代理发送 X-SENDFILE型头。在nginx的的情况下,它也需要一个 X-加速映射头。该文档也有Apache和lighttpd的还有例子。

For whatever reason, Rack::Sendfile requires the front end proxy to send a X-Sendfile-Type header. In the case of nginx it also requires a X-Accel-Mapping header. The documentation also has examples for apache and lighttpd as well.

有人会认为铁轨文档可以链接到机架:: SENDFILE的文档,因为由send_file不开箱,无需额外配置工作。也许我会提交pull请求。

One would think the rails documentation could link to the Rack::Sendfile documentation since send_file does not work out of the box without additional configuration. Perhaps I'll submit a pull request.

在最后,我只需要几行代码添加到我的app.conf:

In the end I only needed to add a couple lines to my app.conf:

upstream unicorn {
  server unix:/tmp/unicorn.app.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  root /vagrant/public;
  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header HOST $http_host;
    proxy_set_header X-Sendfile-Type X-Accel-Redirect; # ADDITION
    proxy_set_header X-Accel-Mapping /=/; # ADDITION
    proxy_redirect off;
    proxy_pass http://localhost:3000;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 5;
}

现在我原来的code按预期工作:

Now my original code works as expected:

def show
  send_file(Video.find(params[:id]).location)
end

编辑:

虽然这个工作最初,它停止工作后,我重新启动我的无业游民盒子,我不得不做出进一步的修改:

Although this worked initially, it stopped working after I restarted my vagrant box and I had to make further changes:

upstream unicorn {
  server unix:/tmp/unicorn.app.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  root /vagrant/public;
  try_files $uri/index.html $uri @unicorn;

  location ~ /files(.*) { # NEW
    internal;             # NEW
    alias $1;             # NEW
  }                       # NEW

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header HOST $http_host;
    proxy_set_header X-Sendfile-Type X-Accel-Redirect;
    proxy_set_header X-Accel-Mapping /=/files/; # CHANGED
    proxy_redirect off;
    proxy_pass http://localhost:3000;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 5;
}

我发现映射一个URL的这整个事情到另一个,然后映射的URI到磁盘上的位置是完全没有必要的。这是无用的,我的用例,我只是映射一个到另一个,然后再返回。 Apache和lighttpd的并不需要它。但至少它的作品。

I find this whole thing of mapping one URI to another and then mapping that URI to a location on disk to be totally unnecessary. It's useless for my use case and I'm just mapping one to another and back again. Apache and lighttpd don't require it. But at least it works.

我还添加了默:: Type.register(视频/ MP4':MP4)配置/初始化/ mime_types.rb 这样的文件送达正确的MIME类型。

I also added Mime::Type.register('video/mp4', :mp4) to config/initializers/mime_types.rb so the file is served with the correct mime type.

这篇关于流的MP4在Chrome中使用Rails,nginx的和由send_file的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆