2015-01-18 21 views
8

के साथ क्रोम में स्ट्रीमिंग एमपी 4 स्ट्रीमिंग मैं अपने जीवन के लिए एचटीएमएलटैग के साथ क्रोम में एमपी 4 स्ट्रीम नहीं कर सकता। अगर मैं public में फ़ाइल छोड़ देता हूं तो सबकुछ ग्रेवी है और उम्मीद के अनुसार काम करता है। लेकिन अगर मैं इसे send_file का उपयोग करके सेवा करने की कोशिश करता हूं, तो कल्पना की जा सकती है कि बहुत कुछ गलत हो जाता है। मैं एक रेल ऐप का उपयोग कर रहा हूं जो Video मॉडल के साथ nginx द्वारा प्रॉक्सी किया गया है जिसमें location विशेषता है जो डिस्क पर एक पूर्ण पथ है।रेल, nginx और send_file

पहले तो मैंने कोशिश की:

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

और मुझे यकीन है कि मैं महिमा है कि आधुनिक वेब विकास है में basking कर लिया जाएगा। हा। यह क्रोम और फ़ायरफ़ॉक्स दोनों में निभाता है, लेकिन न तो खोजता है और न ही कोई विचार है कि वीडियो कितना समय है। मैंने प्रतिक्रिया शीर्षकों पर पोक किया और महसूस किया कि Content-Type को application/octet-stream के रूप में भेजा जा रहा है और Content-Length सेट नहीं है। उम्म ... wth?

ठीक है, मुझे लगता है मैं रेल में उन लोगों के लिए सेट कर सकते हैं: फ़ायरफ़ॉक्स में अपेक्षा के अनुरूप

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

इस बिंदु सब कुछ पर काफी काम करता है। यह जानता है कि वीडियो कितनी देर तक काम करता है और उम्मीद के अनुसार काम करता है। क्रोम को पता चल जाता है कि वीडियो कितना समय है (टाइमस्टैम्प नहीं दिखाता है, लेकिन बार खोज उचित दिखता है) लेकिन मांग काम नहीं करती है।

जाहिर है क्रोम फ़ायरफ़ॉक्स की तुलना में पिकियर है। यह आवश्यक है कि सर्वर 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) 

अब क्रोम तलाश करने का प्रयास है, लेकिन वीडियो रुक और कभी नहीं जब तक पृष्ठ पुनः लोड फिर से काम करता है जब आप ऐसा करेंगे। अरे। इसलिए मैं कर्ल के साथ चारों ओर खेलने के लिए देखने के लिए क्या हो रहा था फैसला किया है और मैं इस खोज की:

$ कर्ल --header "रेंज: बाइट्स = 200-400" http://localhost:8080/videos/1/001.mp4 ftypisomisomiso2avc1mp41 moovlmvhd @ trak \ TKH

$ कर्ल --header "रेंज: बाइट्स = 1200-1400" http://localhost:8080/videos/1/001.mp4 ftypisomisomiso2avc1mp41 moovlmvhd @ trak \ TKH

कोई फर्क नहीं पड़ता बाइट रेंज अनुरोध, डेटा हमेशा फ़ाइल की शुरुआत से शुरू होता है। बाइट्स की उचित मात्रा लौटा दी जाती है (इस मामले में 201 बाइट्स), लेकिन यह हमेशा फाइल की शुरुआत से होती है। स्पष्ट रूप से nginx Content-Length शीर्षलेख का सम्मान करता है लेकिन 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; 
} 

सबसे पहले मैं nginx 1.4.x कि Ubuntu 14.04 के साथ आता है की कोशिश की, तो 1.7 की कोशिश की एक पीपीए से .x - एक ही परिणाम। मैंने apache2 भी कोशिश की और बिल्कुल वही परिणाम थे।

मैं दोहराना चाहूंगा कि वीडियो फ़ाइल समस्या नहीं है। अगर मैं इसे public में छोड़ देता हूं तो nginx उचित माइम प्रकार, शीर्षलेख और क्रोम के लिए ठीक से काम करने के लिए आवश्यक सबकुछ के साथ कार्य करता है।

तो मेरे सवाल का एक दो parter है:

  • क्यों nginx/अपाचे send_file (X-Accel-Redirect/) के साथ यह सब सामान स्वतः प्रबंधन नहीं करती है जब फ़ाइल public से स्थिर परोसा जाता है जैसे कि यह करता है ? रेल में इस सामान को संभालना इतना पीछे है।

  • मैं वास्तव में nginx (या apache) के साथ send_file का उपयोग कैसे कर सकता हूं ताकि क्रोम खुश रहे और मांग की अनुमति दे सके?


अद्यतन 1

ठीक है, तो मैंने सोचा कि मैं अगर मैं प्रॉक्सी के लिए nginx मिल सकता है तस्वीर से बाहर रेल की जटिलता लेने के लिए और बस को देखने की कोशिश करता हूँ सही ढंग से फ़ाइल करें। इसलिए मैंने एक मृत-साधारण नोडज सर्वर को बढ़ाया:

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/'); 

और क्रोम क्लैम के रूप में खुश है। =/curl -I यह भी दिखाता है कि Accept-Ranges: bytes और Content-Type: video/mp4 nginx स्वचालित रूप से डाला जा रहा है - क्योंकि यह होना चाहिए। ऐसा करने से nginx को रोकने से रेल क्या हो सकता है?


अद्यतन 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 

और मेरे ऊपर वर्णित सभी समस्याएं हैं I

लेकिन अगर मैं है:

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 स्वचालित रूप से सेट नहीं करता है जैसे कि यह सरल नोडजे उदाहरण के लिए करता है? मेरे पास config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' सेट है। मैंने इसे उसी परिणाम के साथ application.rb और development.rb के बीच आगे और आगे ले जाया है। मुझे लगता है मैंने कभी उल्लेख नहीं किया ... यह रेल 4.2.0 है।


अद्यतन 3

अब मैं अपने गेंडा सर्वर बदल दिया है पोर्ट 3000 पर सुनने के लिए (के बाद से मैं पहले से ही NodeJS उदाहरण के लिए 3000 पर nginx बदल सुनने के लिए)। अब मैं सीधे यूनिकॉर्न से अनुरोध कर सकता हूं (चूंकि यह बंदरगाह पर सुन रहा है और सॉकेट नहीं है) इसलिए मुझे पता चला है कि curl -I सीधे यूनिकॉर्न शो से पता चलता है कि X-Accel-Redirect हेडर भेजा गया है और केवल curl आईएनसीएनआईएनआईएन वास्तव में फ़ाइल भेजता है। यह send_file जैसा है जो ऐसा नहीं कर रहा है।

उत्तर

5

I अंत में मेरे मूल प्रश्नों के उत्तर हैं। मुझे नहीं लगता था कि मैं कभी यहां आऊंगा। मेरे सभी शोधों ने मृत-अंत, हैकी गैर समाधानों का नेतृत्व किया और "यह सिर्फ बॉक्स से बाहर काम करता है" (ठीक है, मेरे लिए नहीं)।

क्यों nginx/अपाचे यह सब सामान स्वतः send_file (एक्स-एक्सेल-रीडायरेक्ट/एक्स Sendfile) के साथ हैंडल नहीं करता जब फ़ाइल जनता से स्थिर परोसा जाता है जैसे कि यह करता है? रेल में इस सामान को संभालना इतना पीछे है।

वे करते हैं, लेकिन उन्हें रैक :: Sendfile (नीचे देखें) को ठीक से कॉन्फ़िगर करने के लिए कॉन्फ़िगर किया जाना है। रेल में इसे संभालने की कोशिश करना एक हैकी गैर-समाधान है।

मैं वास्तव में कैसे nckx (या apache) के साथ send_file का उपयोग कर सकता हूं ताकि क्रोम खुश रहे और मांग की अनुमति दे सके?

मुझे रैक स्रोत कोड के चारों ओर पोकिंग शुरू करने के लिए काफी हताश हो गया और यही वह जगह है जहां मुझे Rack::Sendfile की टिप्पणियों में मेरा जवाब मिला। उन्हें दस्तावेज़ीकरण के रूप में संरचित किया गया है जो आप rubydoc पर पा सकते हैं।

किसी भी कारण से, Rack::Sendfile को X-Sendfile-Type शीर्षलेख भेजने के लिए फ्रंट एंड प्रॉक्सी की आवश्यकता है। Nginx के मामले में इसे X-Accel-Mapping शीर्षलेख भी आवश्यक है। दस्तावेज़ में अपाचे और lighttpd के लिए भी उदाहरण हैं।

कोई सोचता है कि रेल दस्तावेज रैक :: 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; 
} 

मुझे एक यूआरआई को दूसरे में मैप करने की पूरी चीज मिलती है और फिर यूआरआई को डिस्क पर किसी स्थान पर पूरी तरह से अनावश्यक होने के लिए मैप करना पड़ता है। यह मेरे उपयोग के मामले के लिए बेकार है और मैं सिर्फ एक दूसरे को मैप कर रहा हूं और फिर से वापस आ रहा हूं। अपाचे और lighttpd इसकी आवश्यकता नहीं है। लेकिन कम से कम यह काम करता है।

मैंने Mime::Type.register('video/mp4', :mp4) से config/initializers/mime_types.rb भी जोड़ा ताकि फ़ाइल सही माइम प्रकार के साथ परोसा जा सके।

+0

'स्थान ~/फ़ाइलें (। *)' स्थान ~/files /(.* होना चाहिए '' –

संबंधित मुद्दे