convert audio
This commit is contained in:
+41
-3
@@ -1,6 +1,8 @@
|
|||||||
local cjson = require "cjson"
|
local cjson = require "cjson"
|
||||||
local uuid = require "resty.jit-uuid"
|
local uuid = require "resty.jit-uuid"
|
||||||
local http = require "resty.http"
|
local http = require "resty.http"
|
||||||
|
local upyun_token = require('upyun_token')
|
||||||
|
|
||||||
|
|
||||||
local TaskTypes = {
|
local TaskTypes = {
|
||||||
NOOP = 'NOOP',
|
NOOP = 'NOOP',
|
||||||
@@ -89,7 +91,44 @@ local function get_wx_media(media_id)
|
|||||||
return saved_audio;
|
return saved_audio;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function upload(filename)
|
||||||
|
local upyun_api = ngx.var.upyun_api
|
||||||
|
local token = upyun_token(filename)
|
||||||
|
local file, err = io.open(saved_audio, 'w')
|
||||||
|
local form = {
|
||||||
|
["Authorization"] = token.token,
|
||||||
|
["file"] = file,
|
||||||
|
["policy"] = token.policy
|
||||||
|
}
|
||||||
|
local res, err = httpc:request_uri(
|
||||||
|
upyun_api,
|
||||||
|
{
|
||||||
|
ssl_verify = false, -- 设置参数 ssl_verify 为false 不校验ssl证书
|
||||||
|
method = "POST",
|
||||||
|
body = form,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if res == nil then
|
||||||
|
ngx.log(ngx.ERR, "FAILED TO CONNECT TO *Upyun*", err)
|
||||||
|
ngx.say(err)
|
||||||
|
ngx.exit(500)
|
||||||
|
end
|
||||||
|
|
||||||
|
if 200 ~= res.status then
|
||||||
|
ngx.log(ngx.ERR, 'GET WECHET MEDIA *FAILED*', err)
|
||||||
|
ngx.say(err)
|
||||||
|
ngx.exit(res.status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function convert()
|
local function convert()
|
||||||
|
-- mock for test
|
||||||
|
local res = cjson.encode({
|
||||||
|
r = true, d = "https://upyun.alchemy-studio.cn/daily-music/audios/Song-of-joy.mp3"
|
||||||
|
})
|
||||||
|
ngx.say(res)
|
||||||
|
ngx.exit(200)
|
||||||
|
|
||||||
local file_dir = ngx.var.tmp_file_dir
|
local file_dir = ngx.var.tmp_file_dir
|
||||||
ngx.req.read_body()
|
ngx.req.read_body()
|
||||||
local req_body = cjson.decode(ngx.req.get_body_data())
|
local req_body = cjson.decode(ngx.req.get_body_data())
|
||||||
@@ -108,7 +147,7 @@ local function convert()
|
|||||||
local result, _, code = os.execute(cmd)
|
local result, _, code = os.execute(cmd)
|
||||||
if result and code == 0 then
|
if result and code == 0 then
|
||||||
ngx.log(ngx.INFO, "result -> ", result);
|
ngx.log(ngx.INFO, "result -> ", result);
|
||||||
ngx.say(result)
|
upload(result)
|
||||||
else
|
else
|
||||||
ngx.status = 500
|
ngx.status = 500
|
||||||
ngx.log(ngx.ERR, "AUDIO CONVERT *FAILED*")
|
ngx.log(ngx.ERR, "AUDIO CONVERT *FAILED*")
|
||||||
@@ -118,4 +157,3 @@ local function convert()
|
|||||||
end
|
end
|
||||||
|
|
||||||
convert();
|
convert();
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -15,6 +15,13 @@ server {
|
|||||||
set $resty_loc "/usr/local/openresty";
|
set $resty_loc "/usr/local/openresty";
|
||||||
set $convert "/usr/bin/convert";
|
set $convert "/usr/bin/convert";
|
||||||
|
|
||||||
|
set $upyun_operator "moicen";
|
||||||
|
set $upyun_password = "NyJ51zRwFApY9Wo9EHJMrb8GI9YtvpVN";
|
||||||
|
set $upyun_bucket "huiwing";
|
||||||
|
set $upyun_directory "music-room"
|
||||||
|
set $upyun_cdn "https://upyun.alchemy-studio.cn/music-room/"
|
||||||
|
set $upyun_api "https://v2.api.upyun.com";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
proxy_set_header Host "test-music-room.moicen.com";
|
proxy_set_header Host "test-music-room.moicen.com";
|
||||||
@@ -51,7 +58,7 @@ server {
|
|||||||
content_by_lua_file $resty_loc/resty_funcs/convert_audio.lua;
|
content_by_lua_file $resty_loc/resty_funcs/convert_audio.lua;
|
||||||
}
|
}
|
||||||
#Upyun token
|
#Upyun token
|
||||||
location /api/ngx/upyun_token{
|
location /api/ngx/upyun/token{
|
||||||
content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua;
|
content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua;
|
||||||
}
|
}
|
||||||
#Static file server
|
#Static file server
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ server {
|
|||||||
set $htyuc "http://127.0.0.1:3000"; #htyuc host
|
set $htyuc "http://127.0.0.1:3000"; #htyuc host
|
||||||
|
|
||||||
set $upyun_operator "moicen";
|
set $upyun_operator "moicen";
|
||||||
set $upyun_passwd = "NyJ51zRwFApY9Wo9EHJMrb8GI9YtvpVN";
|
set $upyun_password = "NyJ51zRwFApY9Wo9EHJMrb8GI9YtvpVN";
|
||||||
|
set $upyun_bucket "huiwing";
|
||||||
|
set $upyun_directory "music-room"
|
||||||
|
set $upyun_cdn "https://upyun.alchemy-studio.cn/music-room/"
|
||||||
|
set $upyun_api "https://v2.api.upyun.com";
|
||||||
|
|
||||||
#set $resty_loc "/usr/local/opt/openresty"; # MacOS
|
#set $resty_loc "/usr/local/opt/openresty"; # MacOS
|
||||||
set $resty_loc "/usr/local/openresty"; # CentOS
|
set $resty_loc "/usr/local/openresty"; # CentOS
|
||||||
@@ -92,7 +96,7 @@ server {
|
|||||||
content_by_lua_file $resty_loc/resty_funcs/convert_audio.lua;
|
content_by_lua_file $resty_loc/resty_funcs/convert_audio.lua;
|
||||||
}
|
}
|
||||||
#Upyun token
|
#Upyun token
|
||||||
location /api/ngx/upyun_token{
|
location /api/ngx/upyun/token{
|
||||||
content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua;
|
content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua;
|
||||||
}
|
}
|
||||||
#Static file server
|
#Static file server
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
local http = require("socket.http")
|
||||||
|
local ltn12 = require("ltn12")
|
||||||
|
local lfs = require "lfs"
|
||||||
|
http.TIMEOUT = 5
|
||||||
|
|
||||||
|
local function upload_file ( url, filename )
|
||||||
|
local fileHandle = io.open( filename,"rb")
|
||||||
|
if (fileHandle) then
|
||||||
|
local fileContent = fileHandle:read( "*a" )
|
||||||
|
fileHandle:close()
|
||||||
|
local boundary = 'abcd'
|
||||||
|
local header_b = 'Content-Disposition: form-data; name="file"; filename="' .. filename .. '"\r\nContent-Type: text/plain\r\n'
|
||||||
|
local fileContent = '--' ..boundary .. '\r\n' ..header_b ..'\r\n'.. fileContent .. '\r\n--' .. boundary ..'--\r\n'
|
||||||
|
local response_body = { }
|
||||||
|
local _, code = http.request {
|
||||||
|
url = url ,
|
||||||
|
method = "POST",
|
||||||
|
headers = { ["Content-Length"] = fileContent:len(),
|
||||||
|
['Content-Type'] = 'multipart/form-data; boundary=' .. boundary
|
||||||
|
},
|
||||||
|
source = ltn12.source.string(fileContent) ,
|
||||||
|
sink = ltn12.sink.table(response_body),
|
||||||
|
}
|
||||||
|
return code, table.concat(response_body)
|
||||||
|
else
|
||||||
|
return false, "File Not Found"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rc,content = upload_file ('http://127.0.0.1:826/api/upload', 'test.png' )
|
||||||
|
print(rc,content)
|
||||||
|
|
||||||
|
|
||||||
|
local function calc_token()
|
||||||
|
|
||||||
|
-- Upyun upload parameters
|
||||||
|
local operator = "operator123"
|
||||||
|
local password = "password123"
|
||||||
|
local bucket = "upyun-temp"
|
||||||
|
|
||||||
|
local method = "POST"
|
||||||
|
|
||||||
|
local save_key = "/demo.jpg"
|
||||||
|
-- Get RFC1123 data
|
||||||
|
local date = "Wed, 09 Nov 2016 14:26:58 GMT" -- RFC1123 date
|
||||||
|
-- Generate upyun UCT time
|
||||||
|
local utc_time = os.time(os.date("!*t"))
|
||||||
|
print("utc_time...", utc_time, ngx.http_time(ngx.time()))
|
||||||
|
-- Calculate upyun file expiration
|
||||||
|
local expiration = "1478674618"
|
||||||
|
local content_md5 = "7ac66c0f148de9519b8bd264312c4d64"
|
||||||
|
local policy = string.format("{\"bucket\":\"%s\",\"save-key\":\"%s\",\"expiration\":\"%s\",\"date\":\"%s\",\"content-md5\":\"%s\"}", bucket, save_key, expiration, date, content_md5)
|
||||||
|
print("policy....", policy, ngx.encode_base64(policy))
|
||||||
|
local to_be_signed = method .. "&" .. "/" .. bucket .. "&" .. date .. "&" .. ngx.encode_base64(policy) .. '&' .. content_md5
|
||||||
|
print('to be signed...', to_be_signed)
|
||||||
|
print('md5 pwd...', ngx.md5(password))
|
||||||
|
local hmac_sha1 = ngx.hmac_sha1(ngx.md5(password), to_be_signed)
|
||||||
|
local signature = ngx.encode_base64(hmac_sha1)
|
||||||
|
|
||||||
|
print("generated... " .. signature .. ", expected... k+fHTJndCFAraoeIrd60sJ/8Vb8=")
|
||||||
|
end
|
||||||
|
|
||||||
|
calc_token();
|
||||||
@@ -1,69 +1,799 @@
|
|||||||
strip_path = require("strip_path")
|
-- Copyright (C) Lice Pan (aCayF)
|
||||||
|
|
||||||
local uuid = require "resty.jit-uuid"
|
local md5 = ngx.md5
|
||||||
uuid.seed()
|
local base64 = ngx.encode_base64
|
||||||
|
local http_time = ngx.http_time
|
||||||
|
local time_sec = ngx.time
|
||||||
|
local tcp = ngx.socket.tcp
|
||||||
|
local read_body = ngx.req.read_body
|
||||||
|
local get_body_data = ngx.req.get_body_data
|
||||||
|
local ngx_match = ngx.re.match
|
||||||
|
local ngx_gmatch = ngx.re.gmatch
|
||||||
|
local ngx_print = ngx.print
|
||||||
|
local str_sub = string.sub
|
||||||
|
local lower = string.lower
|
||||||
|
local byte = string.byte
|
||||||
|
local concat = table.concat
|
||||||
|
local insert = table.insert
|
||||||
|
local tostring = tostring
|
||||||
|
local tonumber = tonumber
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local type = type
|
||||||
|
local pairs = pairs
|
||||||
|
|
||||||
local cjson = require "cjson"
|
local HTTP_1_1 = " HTTP/1.1\r\n"
|
||||||
|
|
||||||
-- Upyun upload parameters
|
local _M = { _VERSION = '0.0.1' }
|
||||||
local upyun_operator = ngx.var.$upyun_operator
|
|
||||||
local upyun_passwd = ngx.var.upyun_passwd
|
|
||||||
|
|
||||||
local upyun_expiration = 1800
|
local mt = { __index = _M }
|
||||||
local upyun_method = "POST"
|
|
||||||
|
|
||||||
local function calculate_authorization(bucket, remote_dir, local_filename_with_path)
|
local host_list = {
|
||||||
|
"v0.api.upyun.com",
|
||||||
|
"v1.api.upyun.com",
|
||||||
|
"v2.api.upyun.com",
|
||||||
|
"v3.api.upyun.com"
|
||||||
|
}
|
||||||
|
|
||||||
-- Get filename with extension without path
|
local gmkerl_format = {
|
||||||
local upyun_filename = strip_path.strip_path(local_filename_with_path)
|
type = {
|
||||||
ngx.log(ngx.INFO, "UPYUN FILENAME -> ", upyun_filename)
|
type = {
|
||||||
|
"required",
|
||||||
|
["fix_width"]="allowed", ["fix_height"]="allowed",
|
||||||
|
["fix_width_or_height"]="allowed",
|
||||||
|
["fix_both"]="allowed", ["fix_max"]="allowed",
|
||||||
|
["fix_min"]="allowed", ["fix_scale"]="allowed"
|
||||||
|
},
|
||||||
|
value = {
|
||||||
|
"required",
|
||||||
|
"([1-9][0-9]*)|([1-9][0-9]*x[1-9][0-9]*)"
|
||||||
|
},
|
||||||
|
quality = {
|
||||||
|
"optional",
|
||||||
|
"[1-9][0-9]*"
|
||||||
|
},
|
||||||
|
unsharp = {
|
||||||
|
"optional",
|
||||||
|
["true"]="allowed", ["false"]="allowed"
|
||||||
|
},
|
||||||
|
["exif-switch"] = {
|
||||||
|
"optional",
|
||||||
|
["true"]="allowed", ["false"]="allowed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
thumbnail = {
|
||||||
|
thumbnail = {
|
||||||
|
"optional",
|
||||||
|
"[A-Za-z0-9.]+"
|
||||||
|
},
|
||||||
|
["exif-switch"] = {
|
||||||
|
"optional",
|
||||||
|
["true"]="allowed", ["false"]="allowed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rotate = {
|
||||||
|
rotate = {
|
||||||
|
"required",
|
||||||
|
["auto"]="allowed", ["90"]="allowed",
|
||||||
|
["180"]="allowed", ["270"]="allowed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crop = {
|
||||||
|
crop = {
|
||||||
|
"required",
|
||||||
|
"[0-9]+,[0-9]+,[1-9][0-9]*,[1-9][0-9]*"
|
||||||
|
},
|
||||||
|
["exif-switch"] = {
|
||||||
|
"optional",
|
||||||
|
["true"]="allowed", ["false"]="allowed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
-- Connect upyun save key
|
|
||||||
local upyun_save_key = "/" .. remote_dir .. "/" .. upyun_filename
|
|
||||||
ngx.log(ngx.INFO, "UPYUN SAVE KEY -> ", upyun_save_key)
|
|
||||||
|
|
||||||
-- Get RFC1123 data
|
|
||||||
local upyun_date = ngx.http_time(ngx.time()) -- RFC1123 date
|
|
||||||
ngx.log(ngx.INFO, "UPYUN DATE RFC1123 FORMAT -> ", upyun_date)
|
|
||||||
|
|
||||||
-- Generate upyun UCT time
|
local function _rev_headers(sock)
|
||||||
local upyun_utc_time = os.time(os.date("!*t"))
|
-- return headers, err
|
||||||
ngx.log(ngx.INFO, "UPYUN UTC TIME -> ", upyun_utc_time)
|
local headers = {}
|
||||||
|
|
||||||
-- Calculate upyun file expiration
|
while true do
|
||||||
local upyun_file_expiration = upyun_utc_time + upyun_expiration
|
local line = sock:receive()
|
||||||
ngx.log(ngx.INFO, "UPYUN FILE EXPIRATION -> ", upyun_file_expiration)
|
local m, err = ngx_match(line, [[^([\w-]+)\s*:\s*(.+)$|(^\s*$)]])
|
||||||
|
if err then
|
||||||
|
return nil, "failed to parse received header " .. err
|
||||||
|
end
|
||||||
|
|
||||||
local upyun_policy_json = string.format("{\"bucket\":\"%s\",\"save-key\":\"%s\",\"expiration\":\"%s\",\"date\":\"%s\"}", bucket, upyun_save_key,upyun_file_expiration,upyun_date)
|
if not m then
|
||||||
|
return nil, "invalid received header : " .. line
|
||||||
|
end
|
||||||
|
|
||||||
ngx.log(ngx.INFO, "UPYUN POLICY JSON -> ", upyun_policy_json)
|
if m[3] then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
-- Calculate upyun policy
|
headers[m[1]] = m[2]
|
||||||
local upyun_policy = ngx.encode_base64(upyun_policy_json)
|
end
|
||||||
ngx.say("UPYUN POLICY -> ", upyun_policy)
|
|
||||||
|
|
||||||
---- Calculate MD5 passwd
|
return headers
|
||||||
local upyun_md5_passwd = ngx.md5(upyun_passwd)
|
|
||||||
ngx.log(ngx.INFO, "UPYUN MD5 PASSWD -> ", upyun_md5_passwd)
|
|
||||||
|
|
||||||
---- Connect to be signed string
|
|
||||||
local upyun_to_be_signed_string = upyun_method .. "&" .. "/" .. bucket .. "&" .. upyun_date .. "&" .. upyun_policy
|
|
||||||
ngx.log(ngx.INFO, "UPYUN TO BE SIGNED STRING -> ", upyun_to_be_signed_string)
|
|
||||||
|
|
||||||
-- Calculate upyun token hmac sha1
|
|
||||||
local upyun_token_hmac_sha1 = ngx.hmac_sha1(upyun_md5_passwd, upyun_to_be_signed_string)
|
|
||||||
--ngx.log(ngx.INFO, "UPYUN TOKEN HMAC SHA1 -> ", upyun_token_hmac_sha1)
|
|
||||||
|
|
||||||
-- Calculate upyun signature
|
|
||||||
local upyun_signature = ngx.encode_base64(upyun_token_hmac_sha1)
|
|
||||||
ngx.log(ngx.INFO, "UPYUN SIGNATURE -> ", upyun_signature)
|
|
||||||
|
|
||||||
-- Connect upyun upload file authorization
|
|
||||||
local upyun_upload_file_authorization = string.format("UPYUN %s:%s", upyun_operator, upyun_signature)
|
|
||||||
ngx.log(ngx.INFO, "UPYUN UPLOAD FILE AUTHORIZATION -> ", upyun_upload_file_authorization)
|
|
||||||
|
|
||||||
return upyun_upload_file_authorization
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _receive_length(sock, length)
|
||||||
|
-- return chunk, err
|
||||||
|
local chunk, err = sock:receive(length)
|
||||||
|
if not chunk then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
return chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _receive_chunked(sock, maxsize)
|
||||||
|
-- return chunks, err
|
||||||
|
local chunks = {}
|
||||||
|
|
||||||
|
local size = 0
|
||||||
|
local done = false
|
||||||
|
repeat
|
||||||
|
local str, err = sock:receive() --receive until \r\n
|
||||||
|
if not str then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local length = tonumber(str, 16)
|
||||||
|
|
||||||
|
if not length then
|
||||||
|
return nil, "unable to read chunksize"
|
||||||
|
end
|
||||||
|
|
||||||
|
size = size + length
|
||||||
|
if maxsize and size > maxsize then
|
||||||
|
return nil, 'exceeds maxsize'
|
||||||
|
end
|
||||||
|
|
||||||
|
if length > 0 then
|
||||||
|
local str, err = sock:receive(length)
|
||||||
|
if not str then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
--print(str)
|
||||||
|
insert(chunks, str)
|
||||||
|
else
|
||||||
|
done = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read the \r\n
|
||||||
|
sock:receive(2)
|
||||||
|
until done
|
||||||
|
|
||||||
|
return concat(chunks)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _receive(sock)
|
||||||
|
-- return {}, err
|
||||||
|
local line, err = sock:receive()
|
||||||
|
if not line then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = tonumber(str_sub(line, 10, 12))
|
||||||
|
|
||||||
|
local headers, err = _rev_headers(sock)
|
||||||
|
if not headers then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local length = tonumber(headers["Content-Length"])
|
||||||
|
local body, err
|
||||||
|
--TODO
|
||||||
|
local maxsize = 8096
|
||||||
|
local keepalive = true
|
||||||
|
|
||||||
|
if length then
|
||||||
|
body, err = _receive_length(sock, length)
|
||||||
|
else
|
||||||
|
local encoding = headers["Transfer-Encoding"]
|
||||||
|
if encoding and lower(encoding) == "chunked" then
|
||||||
|
body, err = _receive_chunked(sock, maxsize)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if err then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local connection = headers["Connection"]
|
||||||
|
connection = connection and lower(connection) or nil
|
||||||
|
if connection == "close" then
|
||||||
|
keepalive = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if keepalive then
|
||||||
|
sock:setkeepalive()
|
||||||
|
else
|
||||||
|
sock:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
if status ~= 200 then
|
||||||
|
local info = body
|
||||||
|
if not info then
|
||||||
|
info = str_sub(line, 14, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, info
|
||||||
|
end
|
||||||
|
|
||||||
|
return { status = status, headers = headers, body = body }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _req_header(method, path, headers, extra)
|
||||||
|
-- return req
|
||||||
|
local req = {
|
||||||
|
method,
|
||||||
|
" "
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Append path
|
||||||
|
insert(req, path)
|
||||||
|
|
||||||
|
-- Append version
|
||||||
|
insert(req, HTTP_1_1)
|
||||||
|
|
||||||
|
-- Append headers
|
||||||
|
for key, value in pairs(headers) do
|
||||||
|
insert(req, key .. ": " .. value .. "\r\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Append extra
|
||||||
|
if extra ~= {} and extra ~= nil then
|
||||||
|
for key, value in pairs(extra) do
|
||||||
|
insert(req, key .. ": " .. value .. "\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close headers
|
||||||
|
insert(req, "\r\n")
|
||||||
|
|
||||||
|
return concat(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _request(sock, method, path, headers, body, extra)
|
||||||
|
-- return {}, err
|
||||||
|
local host = headers.Host
|
||||||
|
if not sock then
|
||||||
|
return nil, "not initialized yet"
|
||||||
|
end
|
||||||
|
|
||||||
|
sock:settimeout(5000)
|
||||||
|
|
||||||
|
local ok, err = sock:connect(host, 80)
|
||||||
|
if not ok then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build and send request header
|
||||||
|
local header = _req_header(method, path, headers, extra)
|
||||||
|
local bytes, err = sock:send(header)
|
||||||
|
if not bytes then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Send the body if there is one
|
||||||
|
if body and body.content then
|
||||||
|
local bytes, err = sock:send(body.content)
|
||||||
|
if not bytes then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _receive(sock)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _upyun_request(self, method, path, headers, body, extra)
|
||||||
|
local sock = self.sock
|
||||||
|
local author_mode = self.author_mode
|
||||||
|
local length = headers["Content-Length"]
|
||||||
|
local signature
|
||||||
|
|
||||||
|
if author_mode == "U" then
|
||||||
|
signature = md5(method .. "&" .. path .. "&" .. headers.Date
|
||||||
|
.. "&" .. length
|
||||||
|
.. "&" .. md5(self.passwd))
|
||||||
|
|
||||||
|
headers.Authorization = headers.Authorization .. signature
|
||||||
|
end
|
||||||
|
|
||||||
|
return _request(sock, method, path, headers, body, extra)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _is_dir(path)
|
||||||
|
-- return true or false
|
||||||
|
return str_sub(path, -1, -1) == "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _parse_gmkerl(gmkerl, extra)
|
||||||
|
-- return ok, err
|
||||||
|
local expect_format = true
|
||||||
|
local format
|
||||||
|
|
||||||
|
for k, v in pairs(gmkerl_format) do
|
||||||
|
-- find the format
|
||||||
|
if gmkerl[k] and gmkerl[k] ~= "" then
|
||||||
|
if not expect_format then
|
||||||
|
return nil, "duplicated format"
|
||||||
|
end
|
||||||
|
|
||||||
|
format = v
|
||||||
|
expect_format = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if expect_format then
|
||||||
|
return nil, "invalid gmkerl"
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(format) do
|
||||||
|
local value = tostring(gmkerl[k])
|
||||||
|
if value == "nil" or value == "" then
|
||||||
|
if v[1] == "required" then
|
||||||
|
return nil, "missing required arg : " .. k
|
||||||
|
end
|
||||||
|
|
||||||
|
--do nothing to optinal arg
|
||||||
|
else
|
||||||
|
if v[value] ~= "allowed" then
|
||||||
|
if not v[2] then
|
||||||
|
return nil, 'invalid value "'
|
||||||
|
.. value .. '" to ' .. k
|
||||||
|
end
|
||||||
|
|
||||||
|
-- regex is stored in the v[2]
|
||||||
|
local m ,err = ngx_match(value, v[2])
|
||||||
|
if err then
|
||||||
|
return nil, "error occurs when matching "
|
||||||
|
.. value .. " with " .. v[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
if not m then
|
||||||
|
return nil, 'invalid value "'
|
||||||
|
.. value .. '" to ' .. k
|
||||||
|
end
|
||||||
|
|
||||||
|
value = m[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
extra["x-gmkerl-" .. k] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _format_path(path, legal_path)
|
||||||
|
-- return path, err
|
||||||
|
if not path or type(path) ~= "string" or path == "" then
|
||||||
|
return nil, "invalid path : " .. path
|
||||||
|
end
|
||||||
|
|
||||||
|
if legal_path == "dir" and not _is_dir(path) then
|
||||||
|
return nil, path .. " is not a legal directory name"
|
||||||
|
end
|
||||||
|
|
||||||
|
if legal_path == "file" and _is_dir(path)then
|
||||||
|
return nil, path .. " is not a legal file name"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- checking is not needed when legal_path is "dir_or_file"
|
||||||
|
|
||||||
|
-- pre insert a "/" if needed
|
||||||
|
path = str_sub(path, 1, 1) == "/" and path or "/" .. path
|
||||||
|
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _parse_upyun_option(option, extra, content)
|
||||||
|
-- return modified extra
|
||||||
|
local mkdir = tostring(option.mkdir)
|
||||||
|
local omd5 = tostring(option.md5)
|
||||||
|
local secret = option.secret
|
||||||
|
local otype = option.type
|
||||||
|
|
||||||
|
if mkdir == "true" or mkdir == "false" then
|
||||||
|
extra["Mkdir"] = mkdir
|
||||||
|
end
|
||||||
|
|
||||||
|
if omd5 == "true" then
|
||||||
|
extra["Content-MD5"] = md5(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(secret) == "string" then
|
||||||
|
extra["Content-Secret"] = secret
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(otype) == "string" then
|
||||||
|
extra["Content-Type"] = otype
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _parse_upyun_headers(headers, regex)
|
||||||
|
-- return info, err
|
||||||
|
if not headers or type(headers) ~= "table" then
|
||||||
|
return nil, "missing recieved headers"
|
||||||
|
end
|
||||||
|
|
||||||
|
local info = {}
|
||||||
|
for k, v in pairs(headers) do
|
||||||
|
|
||||||
|
local m, err = ngx_match(k, regex)
|
||||||
|
if err then
|
||||||
|
return nil, "failed to parse upyun headers " .. err
|
||||||
|
end
|
||||||
|
|
||||||
|
if m then
|
||||||
|
info[m[1]] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return info
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function _parse_upyun_body(body)
|
||||||
|
-- return info, err
|
||||||
|
if not body or type(body) ~= "string" then
|
||||||
|
return nil, "missing recieved body"
|
||||||
|
end
|
||||||
|
|
||||||
|
local iterator, err = ngx_gmatch(body, [[([^\n\t]+)(\n|\t|$)]])
|
||||||
|
if not iterator then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local i = 1
|
||||||
|
local j = 1
|
||||||
|
local file
|
||||||
|
local info = {}
|
||||||
|
local key = {"name", "type", "size", "lastmodified"}
|
||||||
|
while true do
|
||||||
|
local m, err = iterator()
|
||||||
|
if err then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
if not m then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if j == 1 then
|
||||||
|
info[i] = {}
|
||||||
|
file = info[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
file[key[j]] = m[1]
|
||||||
|
j = j + 1
|
||||||
|
|
||||||
|
if m[2] == "\n" then
|
||||||
|
if j ~= 5 then
|
||||||
|
return nil, "invalid upyun body " .. body
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
j = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return info
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.new(self, config)
|
||||||
|
local user = config.user
|
||||||
|
local passwd = config.passwd
|
||||||
|
local endpoint = config.endpoint and tonumber(config.endpoint) + 1 or 1
|
||||||
|
local author = config.author and lower(config.author) or nil
|
||||||
|
|
||||||
|
if not user or type(user) ~= "string" or user == "" then
|
||||||
|
return nil, "invalid user"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not passwd or type(passwd) ~= "string" or passwd == "" then
|
||||||
|
return nil, "invalid passwd"
|
||||||
|
end
|
||||||
|
|
||||||
|
if endpoint > 4 then
|
||||||
|
return nil, "invalid endpoint"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- explicit "basic" sets author_mode to Basic
|
||||||
|
local author_mode = "U"
|
||||||
|
if author == "basic" then
|
||||||
|
author_mode = "B"
|
||||||
|
author = "Basic " .. base64(user .. ":" .. passwd)
|
||||||
|
else
|
||||||
|
author = "UpYun " .. user .. ":"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- file to be uploaded is stored in the request body
|
||||||
|
read_body()
|
||||||
|
local content = get_body_data()
|
||||||
|
local file = ngx.req.get_body_file()
|
||||||
|
if file then
|
||||||
|
local f, err = io.open(file, "r")
|
||||||
|
if not f then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
content = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
--if not content then
|
||||||
|
-- return nil, "request body is expected"
|
||||||
|
--end
|
||||||
|
|
||||||
|
--TODO ngx.updatetime?
|
||||||
|
local date = http_time(time_sec())
|
||||||
|
if not date then
|
||||||
|
return nil, "failed to get current time"
|
||||||
|
end
|
||||||
|
|
||||||
|
local sock = tcp()
|
||||||
|
if not sock then
|
||||||
|
return nil, "failed to create a TCP socket"
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable ({
|
||||||
|
sock = sock,
|
||||||
|
user = user,
|
||||||
|
passwd = passwd,
|
||||||
|
author_mode = author_mode,
|
||||||
|
headers = {
|
||||||
|
Authorization = author,
|
||||||
|
Host = host_list[endpoint],
|
||||||
|
Date = date,
|
||||||
|
["Content-Length"] = "0"
|
||||||
|
},
|
||||||
|
body = {content = content},
|
||||||
|
}, mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.upload_file(self, path, gmkerl, option)
|
||||||
|
-- return info, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local body = self.body
|
||||||
|
local content = body.content
|
||||||
|
local legal_path = "file"
|
||||||
|
local extra = {}
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
if gmkerl and type(gmkerl) == "table" and gmkerl ~= {} then
|
||||||
|
local ok, err = _parse_gmkerl(gmkerl, extra)
|
||||||
|
if not ok then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- file to be uploaded is stored in the request body
|
||||||
|
if not content or content == "" then
|
||||||
|
return nil, "request body is expected"
|
||||||
|
end
|
||||||
|
|
||||||
|
if option and type(option) == "table" then
|
||||||
|
_parse_upyun_option(option, extra, content)
|
||||||
|
end
|
||||||
|
|
||||||
|
headers["Content-Length"] = tostring(#content)
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "PUT", path, headers, body, extra)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _parse_upyun_headers(ret.headers, [[^x-upyun-([\w-]+)$]])
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
-- write the original author back as header.Authorization
|
||||||
|
-- may be changed in the _upyun_request()
|
||||||
|
headers.Authorization = author
|
||||||
|
headers["Content-Length"] = "0"
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.download_file(self, path)
|
||||||
|
-- return file, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "file"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "GET", path, headers)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return ret.body
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.get_fileinfo(self, path)
|
||||||
|
-- return info, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "file"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "HEAD", path, headers)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _parse_upyun_headers(ret.headers, [[^x-upyun-file-([\w-]+)$]])
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.remove_file(self, path)
|
||||||
|
-- return ok, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "dir_or_file"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "DELETE", path, headers)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.make_dir(self, path, option)
|
||||||
|
-- return ok, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "dir"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local extra = { Folder = "true" }
|
||||||
|
if option and type(option) == "table" then
|
||||||
|
_parse_upyun_option(option, extra)
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "POST", path, headers, nil, extra)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.read_dir(self, path)
|
||||||
|
-- return items, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "dir"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "GET", path, headers)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
ret, err = _parse_upyun_body(ret.body)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _M.get_usage(self, path)
|
||||||
|
-- return usage, err
|
||||||
|
local headers = self.headers
|
||||||
|
local author = headers.Authorization
|
||||||
|
local legal_path = "dir_or_file"
|
||||||
|
local ret, err
|
||||||
|
|
||||||
|
path, err = _format_path(path, legal_path)
|
||||||
|
if not path then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
path = path .. "?usage"
|
||||||
|
|
||||||
|
ret, err = _upyun_request(self, "GET", path, headers)
|
||||||
|
if not ret then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.Authorization = author
|
||||||
|
|
||||||
|
return ret.body
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return _M
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
local strip_path = require("strip_path")
|
||||||
|
|
||||||
|
-- filename eg. /file_upload/xxxx-xxxx-xxxx-xxxx.mp3
|
||||||
|
local function calc_token(filename)
|
||||||
|
|
||||||
|
-- Upyun upload parameters
|
||||||
|
local operator = ngx.var.upyun_operator
|
||||||
|
local password = ngx.var.upyun_password
|
||||||
|
local bucket = ngx.var.upyun_bucket
|
||||||
|
local directory = ngx.var.upyun_directory
|
||||||
|
|
||||||
|
local expiration = 1800
|
||||||
|
local method = "POST"
|
||||||
|
|
||||||
|
local save_key = "/" .. directory .. "/" .. strip_path.strip_path(filename)
|
||||||
|
print("save key...", save_key)
|
||||||
|
-- Get RFC1123 data
|
||||||
|
local date = ngx.http_time(ngx.time()) -- RFC1123 date
|
||||||
|
print("date ...", date)
|
||||||
|
-- Generate upyun UCT time
|
||||||
|
local utc_time = os.time(os.date("!*t"))
|
||||||
|
-- Calculate upyun file expiration
|
||||||
|
expiration = utc_time + expiration
|
||||||
|
print("expiration ...", expiration)
|
||||||
|
-- 不能使用cjson,因为lua的table没有顺序,cjson encode出来的顺序会错乱
|
||||||
|
local policy = string.format("{\"bucket\":\"%s\",\"save-key\":\"%s\",\"expiration\":\"%s\",\"date\":\"%s\"}", bucket, save_key, expiration, date)
|
||||||
|
local to_be_signed = method .. "&" .. "/" .. bucket .. "&" .. date .. "&" .. ngx.encode_base64(policy)
|
||||||
|
print("to be signed ...", to_be_signed)
|
||||||
|
local hmac_sha1 = ngx.hmac_sha1(ngx.md5(password), to_be_signed)
|
||||||
|
local signature = ngx.encode_base64(hmac_sha1)
|
||||||
|
|
||||||
|
return { token = string.format("UPYUN %s:%s", operator, signature), policy = policy }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return calc_token
|
||||||
Reference in New Issue
Block a user