ローカルで運用しているnginxサーバー上のmp4ファイルをchrome経由で閲覧したときに、
シークができなかったので、HTTPのRange Requestsをluaで実装した。
ローカルでの運用を考えているため、セキュリティ対策など考えられていません。
流用の際は十分ご注意ください。
記事内のsample.comは各自の環境に置き換えてください。
環境
- Ubuntu server 16.04.1
- kernel: 4.4.0-57-generic
インストール
$ sudo apt-get install nginx nginx-extras
$ dpkg -l | grep nginx
ii nginx 1.10.0-0ubuntu0.16.04.4 all small, powerful, scalable web/proxy server
ii nginx-common 1.10.0-0ubuntu0.16.04.4 all small, powerful, scalable web/proxy server - common files
ii nginx-extras 1.10.0-0ubuntu0.16.04.4 amd64 nginx web/proxy server (extended version)
nginx-lua-moduleの動作確認
まずは、helloコマンドを実装。
nginxのsite設定
$ sudo vim /etc/nginx/sites-available/dl.sample.com
server {
listen 443 ssl;
server_name dl.sample.com;
location / {
root /var/www/html;
}
location /hello {
content_by_lua "
ngx.say('Hello, from inline lua')
";
}
location /hellofile {
content_by_lua_file /etc/nginx/lua/hello.lua;
}
}
$ sudo ln -s /etc/nginx/sites-available/dl.sample.com /etc/nginx/sites-enable/
luaファイル作成(location /hellofileで呼び出されるもの)
$ sudo mkdir /etc/nginx/lua
$ sudo vim /etc/nginx/lua/hello.lua
ngx.say("hello by file")
動作確認
$ sudo service nginx restart
$ curl https://dl.sample.com/hello
Hello, from inline lua
$ curl https://dl.sample.com/hellofile
hello by file
Range Requestsの実装
nginxのsite設定
$ sudo vim /etc/nginx/sites-available/dl.sample.com
server {
listen 443 ssl;
server_name dl.sample.com;
location / {
root /var/www/html;
}
location ~ /v0/(.*\.mp4) {
set $video_path $1;
content_by_lua_file /etc/nginx/lua/v0/anymp4.lua;
}
}
任意のファイル名でアクセスできるように(.*.mp4)設定。
luaファイル作成(location /v0/(.*.mp4) で呼び出されるもの)
$ sudo mkdir /etc/nginx/lua/v0
$ sudo vim /etc/nginx/lua/v0/anymp4.lua
function io.file_size(filename)
local fh = assert(io.open(filename, "rb"))
local len = assert(fh:seek("end"))
fh.close()
return len
end
function table.in_key(tbl, key)
for k, v in pairs(tbl) do
if k==key then return true end
end
end
local video_path = ngx.unescape_uri(ngx.var.video_path)
local video_dir = "/mnt/mp4/"
local filename = video_dir..video_path
local h = ngx.req.get_headers()
local instance_length = io.file_size(filename)
if table.in_key(h, "range") then
-- Range requests
local reqRange = h["range"]
local first_byte_pos = 0
local last_byte_pos = instance_length - 1
local reqFirst, reqLast = string.match(reqRange, "(%d*)-(%d*)")
first_byte_pos = reqFirst
if reqLast == "" then
-- nothing to do
else
last_byte_pos = tonumber(reqLast)
end
-- Status code
ngx.status = ngx.HTTP_PARTIAL_CONTENT
-- Response headers
contentLength = last_byte_pos - first_byte_pos + 1
ngx.header["Content-Length"] = contentLength
ngx.header["Content-Range"] = "bytes "..first_byte_pos.."-"..last_byte_pos.."/"..instance_length
-- output stream
local file = io.open(filename)
file:seek("set", tonumber(reqFirst))
local f = file:read(contentLength)
ngx.print(f)
else
-- Normal requests
ngx.header["Content-Length"] = instance_length
-- Status code
ngx.status = ngx.HTTP_OK
-- output stream
local file = io.open(filename)
local f = file:read("*all")
ngx.print(f)
ngx.flush(true)
end
RFC7233に従い、リクエスト時にHTTPヘッダ内にrangeがあれば、
206(HTTP_PARTIAL_CONTENT)と指定された範囲のデータを送信する。
動作確認
$ sudo service nginx restart
$ ls /mnt/mp4/
1.mp4
任意のmp4ファイル(この例では1.mp4)をanymp4.luaで指定したフォルダ(この例では/mnt/mp4/)に格納しておく。
chromeで下記のようなアドレスへアクセスすると、mp4閲覧時にシーク可能となっていることが確認できる。
https://dl.sample.com/v0/1.mp4
参考
http://techhey.hatenablog.com/entry/2014/04/17/200831
https://triple-underscore.github.io/RFC7233-ja.html