gpt4 book ai didi

ruby-on-rails - 使用 rails、nginx 和 send_file 在 Chrome 中流式传输 mp4

转载 作者:行者123 更新时间:2023-12-04 04:17:02 27 4
gpt4 key购买 nike

我这辈子都不能用 html5 将 mp4 流式传输到 Chrome <video>标签。如果我将文件放在 public那么一切都是肉汁,并按预期工作。但是如果我尝试使用 send_file 提供服务,几乎所有可以想象的都会出错。我正在使用由 nginx 代理的 rails 应用程序,带有 Video具有 location 的模型属性是磁盘上的绝对路径。

起初我试过:

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

我确信我会沉浸在现代 Web 开发的荣耀中。哈。这会在 Chrome 和 Firefox 中播放,但既不搜索也不知道视频的长度。我戳了一下响应头并意识到 Content-Type正在发送为 application/octet-stream并且没有 Content-Length放。嗯……什么?

好的,我想我可以在 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

在这一点上,一切都在 Firefox 中按预期工作。它知道视频的长度并按预期进行搜索。 Chrome 似乎知道视频的长度(不显示时间戳,但搜索栏看起来很合适)但搜索不起作用。

显然 Chrome 比 Firefox 更挑剔。它要求服务器以 Accept-Ranges 响应。带有值的标题 bytes并使用 206 响应后续请求(发生在用户搜索时)以及文件的适当部分。

好的,所以我从 here 借了一些代码然后我有这个:
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 会尝试搜索,但是当您执行此操作时,视频会停止播放,并且在页面重新加载之前不再工作。啊。所以我决定玩 curl 看看发生了什么,我发现了这一点:

$ curl --header "Range: bytes=200-400" http://localhost:8080/videos/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 header 但忽略 Content-Range标题。

我的 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 非常基本:
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 并得到了完全相同的结果。

我想重申视频文件不是问题。如果我把它放在 public然后 nginx 为它提供适当的 mime 类型、标题和 Chrome 正常工作所需的一切。

所以我的问题是一个两部分:
  • 为什么 nginx/apache 不使用 send_file 自动处理所有这些东西( X-Accel-Redirect/X-Sendfile ) 就像从 public 静态提供文件时一样?在 Rails 中处理这些东西太落后了。
  • 我到底如何才能将 send_file 与 nginx(或 apache)一起使用,以便 Chrome 会很高兴并允许搜索?


  • Update 1



    好的,所以我想我会尝试将 rails 的复杂性排除在外,看看是否可以让 nginx 正确代理文件。所以我启动了一个非常简单的 nodjs 服务器:
    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/');

    Chrome 像蛤蜊一样快乐。 =/ curl -I甚至显示 Accept-Ranges: bytesContent-Type: video/mp4正在由 nginx 自动插入 - 应该是这样。 rails 会做什么阻止 nginx 这样做?

    Update 2



    我可能越来越近了...

    如果我有:
    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 不设置 Content-Type像简单的 nodejs 示例一样自动在这里?我有 config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'放。我在 application.rb 之间来回移动了它和 development.rb结果相同。我想我从来没有提到过……这是 Rails 4.2.0。

    Update 3



    现在我已经改变了我的 unicorn 服务器来监听端口 3000(因为我已经改变了 nginx 来监听 nodejs 示例的 3000)。现在我可以直接向 unicorn 发出请求(因为它正在监听端口而不是套接字)所以我发现 curl -I直接给 unicorn 显示没有 X-Accel-Redirect头被发送,只是 curl ing unicorn 直接实际发送文件。就像 send_file没有做它应该做的。

    最佳答案

    我终于有了最初问题的答案。我没想到我会来到这里。我所有的研究都导致了死胡同、骇人听闻的非解决方案和“它只是开箱即用”(好吧,不适合我)。

    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.



    它们可以,但必须正确配置才能取悦 Rack::Sendfile(见下文)。试图在 Rails 中处理这个问题是一个非常棘手的非解决方案。

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



    我迫不及待地开始研究机架源代码,这就是我在 Rack::Sendfile 的评论中找到答案的地方。 .它们被组织成文档,您可以在 rubydoc 上找到这些文档。 .

    无论出于何种原因, Rack::Sendfile要求前端代理发送 X-Sendfile-Type标题。在 nginx 的情况下,它还需要一个 X-Accel-Mapping标题。该文档也有 apache 和 lighttpd 的示例。

    有人会认为 rails 文档可以链接到 Rack::Sendfile 文档,因为 send_file 在没有额外配置的情况下无法开箱即用。也许我会提交拉取请求。

    最后,我只需要在 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;
    }

    现在我的原始代码按预期工作:
    def show
    send_file(Video.find(params[:id]).location)
    end

    编辑:

    虽然这最初有效,但在我重新启动流浪箱后它停止工作,我不得不进行进一步的更改:
    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;
    }

    我发现将一个 URI 映射到另一个,然后将该 URI 映射到磁盘上的某个位置的整个过程是完全没有必要的。它对我的用例毫无用处,我只是将一个映射到另一个然后再映射回来。 Apache 和 lighttpd 不需要它。但至少它有效。

    我还加了 Mime::Type.register('video/mp4', :mp4)config/initializers/mime_types.rb因此该文件以正确的 mime 类型提供。

    关于ruby-on-rails - 使用 rails、nginx 和 send_file 在 Chrome 中流式传输 mp4,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28008564/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com