diff --git a/convert_audio.lua b/convert_audio.lua index 3cd460f..455ad06 100644 --- a/convert_audio.lua +++ b/convert_audio.lua @@ -1,6 +1,8 @@ local cjson = require "cjson" -local uuid = require "resty.jit-uuid" +local uuid = require "resty.jit-uuid" local http = require "resty.http" +local upyun_token = require('upyun_token') + local TaskTypes = { NOOP = 'NOOP', @@ -89,7 +91,44 @@ local function get_wx_media(media_id) return saved_audio; 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() + -- 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 ngx.req.read_body() local req_body = cjson.decode(ngx.req.get_body_data()) @@ -108,7 +147,7 @@ local function convert() local result, _, code = os.execute(cmd) if result and code == 0 then ngx.log(ngx.INFO, "result -> ", result); - ngx.say(result) + upload(result) else ngx.status = 500 ngx.log(ngx.ERR, "AUDIO CONVERT *FAILED*") @@ -118,4 +157,3 @@ local function convert() end convert(); - diff --git a/music-room-dev.conf b/music-room-dev.conf index 787d10d..fb2b39a 100644 --- a/music-room-dev.conf +++ b/music-room-dev.conf @@ -15,6 +15,13 @@ server { set $resty_loc "/usr/local/openresty"; 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 / { try_files $uri $uri/ /index.html; 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; } #Upyun token - location /api/ngx/upyun_token{ + location /api/ngx/upyun/token{ content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua; } #Static file server diff --git a/music-room-test.conf b/music-room-test.conf index 01b827d..f8df25e 100644 --- a/music-room-test.conf +++ b/music-room-test.conf @@ -50,7 +50,11 @@ server { set $htyuc "http://127.0.0.1:3000"; #htyuc host 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/openresty"; # CentOS @@ -92,7 +96,7 @@ server { content_by_lua_file $resty_loc/resty_funcs/convert_audio.lua; } #Upyun token - location /api/ngx/upyun_token{ + location /api/ngx/upyun/token{ content_by_lua_file $resty_loc/resty_funcs/upyun_token.lua; } #Static file server diff --git a/test_upyun_token.lua b/test_upyun_token.lua new file mode 100644 index 0000000..498a532 --- /dev/null +++ b/test_upyun_token.lua @@ -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(); \ No newline at end of file diff --git a/upyun.lua b/upyun.lua index f95c030..f7c5f90 100644 --- a/upyun.lua +++ b/upyun.lua @@ -1,69 +1,799 @@ -strip_path = require("strip_path") +-- Copyright (C) Lice Pan (aCayF) -local uuid = require "resty.jit-uuid" -uuid.seed() +local md5 = ngx.md5 +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 upyun_operator = ngx.var.$upyun_operator -local upyun_passwd = ngx.var.upyun_passwd +local _M = { _VERSION = '0.0.1' } -local upyun_expiration = 1800 -local upyun_method = "POST" +local mt = { __index = _M } -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 upyun_filename = strip_path.strip_path(local_filename_with_path) - ngx.log(ngx.INFO, "UPYUN FILENAME -> ", upyun_filename) +local gmkerl_format = { + type = { + 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 upyun_utc_time = os.time(os.date("!*t")) - ngx.log(ngx.INFO, "UPYUN UTC TIME -> ", upyun_utc_time) +local function _rev_headers(sock) + -- return headers, err + local headers = {} - -- Calculate upyun file expiration - local upyun_file_expiration = upyun_utc_time + upyun_expiration - ngx.log(ngx.INFO, "UPYUN FILE EXPIRATION -> ", upyun_file_expiration) + while true do + local line = sock:receive() + 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 - local upyun_policy = ngx.encode_base64(upyun_policy_json) - ngx.say("UPYUN POLICY -> ", upyun_policy) + headers[m[1]] = m[2] + end - ---- Calculate MD5 passwd - 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 + return headers 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 \ No newline at end of file diff --git a/upyun_token.lua b/upyun_token.lua new file mode 100644 index 0000000..7111d35 --- /dev/null +++ b/upyun_token.lua @@ -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 \ No newline at end of file