diff --git a/orange/lib/globalpatches.lua b/orange/lib/globalpatches.lua new file mode 100644 index 00000000..6128cacf --- /dev/null +++ b/orange/lib/globalpatches.lua @@ -0,0 +1,24 @@ +--- +-- Override global(_G) namepace, inspired by kong +-- +return function(opts) + -- For further setting + opts = opts or {} + + -- Add a special randomseed for math, no arguments + do + local util = require "orange.utils.utils" + local randomseed = math.randomseed + local seed + + _G.math.randomseed = function() + if not seed then + seed = util.get_random_seed() + else + ngx.log(ngx.ERR, "The seed random number generator seed has already seeded with: " .. seed .. "\n") + end + randomseed(seed) + return seed + end + end +end diff --git a/orange/lib/jit-uuid.lua b/orange/lib/jit-uuid.lua new file mode 100644 index 00000000..cb06f9d9 --- /dev/null +++ b/orange/lib/jit-uuid.lua @@ -0,0 +1,400 @@ +-- +-- https://github.com/thibaultcha/lua-resty-jit-uuid/blob/master/lib/resty/jit-uuid.lua +-- commit 9c187e17952d2d1b03d84db082c64be88bbaedb2 +-- + +-- vim:set ts=4 sw=4 et: + +--- jit-uuid +-- Fast and dependency-free UUID library for LuaJIT/ngx_lua. +-- @module jit-uuid +-- @author Thibault Charbonnier +-- @license MIT +-- @release 0.0.5 + + +local bit = require 'bit' + + +local tohex = bit.tohex +local band = bit.band +local bor = bit.bor + + +local _M = { + _VERSION = '0.0.5' +} + + +---------- +-- seeding +---------- + + +--- Seed the random number generator. +-- Under the hood, this function calls `math.randomseed`. +-- It makes sure to use the most appropriate seeding technique for +-- the current environment, guaranteeing a unique seed. +-- +-- To guarantee unique UUIDs, you must have correctly seeded +-- the Lua pseudo-random generator (with `math.randomseed`). +-- You are free to seed it any way you want, but this function +-- can do it for you if you'd like, with some added guarantees. +-- +-- @param [ t y p e = n u m b e r ] seed (Optional) A seed to use. If none given, will +-- generate one trying to use the most appropriate technique. +-- @treturn number `seed`: the seed given to `math.randomseed`. +-- @usage +-- local uuid = require 'resty.jit-uuid' +-- uuid.seed() +-- +-- -- in ngx_lua, seed in the init_worker context: +-- init_worker_by_lua { +-- local uuid = require 'resty.jit-uuid' +-- uuid.seed() +-- } +function _M.seed(seed) + if not seed then + if ngx then + seed = ngx.time() + ngx.worker.pid() + + elseif package.loaded['socket'] and package.loaded['socket'].gettime then + seed = package.loaded['socket'].gettime() * 10000 + + else + seed = os.time() + end + end + + math.randomseed(seed) + + return seed +end + + +------------- +-- validation +------------- + + +do + if ngx and string.find(ngx.config.nginx_configure(), '--with-pcre-jit', nil, true) then + local type = type + local re_find = ngx.re.find + local regex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' + + + --- Validate a string as a UUID. + -- To be considered valid, a UUID must be given in its canonical + -- form (hexadecimal digits including the hyphen characters). + -- This function validates UUIDs disregarding their generation algorithm, + -- and in a case-insensitive manner, but checks the variant field. + -- + -- Use JIT PCRE if available in OpenResty or fallbacks on Lua patterns. + -- + -- @param [ t y p e = s t r i n g ] str String to verify. + -- @treturn boolean `valid`: true if valid UUID, false otherwise. + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9bZZZZZZZZZ' --> false + -- uuid.is_valid 'cbb297c0-a956-486d-dd1d-f9b42df9465a' --> false (invalid variant) + -- uuid.is_valid 'cbb297c0a956486dad1df9b42df9465a' --> false (no dashes) + -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9b42df9465a' --> true + function _M.is_valid(str) + -- it has proven itself efficient to first check the length with an + -- evenly distributed set of valid and invalid uuid lengths. + if type(str) ~= 'string' or #str ~= 36 then + return false + end + + return re_find(str, regex, 'ioj') ~= nil + end + + else + local match = string.match + local d = '[0-9a-fA-F]' + local p = '^' .. table.concat({ + d:rep(8), + d:rep(4), + d:rep(4), + '[89ab]' .. d:rep(3), + d:rep(12) + }, '%-') .. '$' + + + function _M.is_valid(str) + if type(str) ~= 'string' or #str ~= 36 then + return false + end + + return match(str, p) ~= nil + end + end +end + + +---------------- +-- v4 generation +---------------- + + +do + local fmt = string.format + local random = math.random + + + --- Generate a v4 UUID. + -- v4 UUIDs are created from randomly generated numbers. + -- + -- @treturn string `uuid`: a v4 (randomly generated) UUID. + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- local u1 = uuid() ---> __call metamethod + -- local u2 = uuid.generate_v4() + function _M.generate_v4() + return fmt('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s', + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + + tohex(bor(band(random(0, 255), 0x0F), 0x40), 2), + tohex(random(0, 255), 2), + + tohex(bor(band(random(0, 255), 0x3F), 0x80), 2), + tohex(random(0, 255), 2), + + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2), + tohex(random(0, 255), 2)) + end +end + + +---------------- +-- v3/v5 generation +---------------- + + +do + if ngx then + local tonumber = tonumber + local assert = assert + local concat = table.concat + local gmatch = string.gmatch + local type = type + local char = string.char + local fmt = string.format + local sub = string.sub + local buf = {} + + + local function factory(namespace, hash_fn) + if not _M.is_valid(namespace) then + return nil, 'namespace must be a valid UUID' + end + + local i = 0 + local iter = gmatch(namespace, '([%a%d][%a%d])') -- pattern faster than PCRE without resty.core + + while true do + local m = iter() + if not m then + break + end + + i = i + 1 + buf[i] = char(tonumber(m, 16)) + end + + assert(i == 16, "invalid binary namespace buffer length") + + return function(name) + if type(name) ~= 'string' then + return nil, 'name must be a string' + end + + local hash, ver, var = hash_fn(concat(buf, ''), name) + + return fmt('%s-%s-%s%s-%s%s-%s', sub(hash, 1, 8), + sub(hash, 9, 12), + ver, + sub(hash, 15, 16), + var, + sub(hash, 19, 20), + sub(hash, 21, 32)) + end + end + + + --- Instanciate a v3 UUID factory. + -- @function factory_v3 + -- Creates a closure generating namespaced v3 UUIDs. + -- @param [ t y p e = s t r i n g ] namespace (must be a valid UUID according to `is_valid`) + -- @treturn function `factory`: a v3 UUID generator. + -- @treturn string `err`: a string describing an error + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- local fact = assert(uuid.factory_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d')) + -- + -- local u1 = fact('hello') + -- --- > 3db7a435-8c56-359d-a563-1b69e6802c78 + -- + -- local u2 = fact('foobar') + -- --- > e8d3eeba-7723-3b72-bbc5-8f598afa6773 + do + local md5 = ngx.md5 + + + local function v3_hash(binary, name) + local hash = md5(binary .. name) + return hash, + tohex(bor(band(tonumber(sub(hash, 13, 14), 16), 0x0F), 0x30), 2), + tohex(bor(band(tonumber(sub(hash, 17, 18), 16), 0x3F), 0x80), 2) + end + + + function _M.factory_v3(namespace) + return factory(namespace, v3_hash) + end + end + + + --- Instanciate a v5 UUID factory. + -- @function factory_v5 + -- Creates a closure generating namespaced v5 UUIDs. + -- @param [ t y p e = s t r i n g ] namespace (must be a valid UUID according to `is_valid`) + -- @treturn function `factory`: a v5 UUID generator. + -- @treturn string `err`: a string describing an error + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- local fact = assert(uuid.factory_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d')) + -- + -- local u1 = fact('hello') + -- --- > 4850816f-1658-5890-8bfd-1ed14251f1f0 + -- + -- local u2 = fact('foobar') + -- --- > c9be99fc-326b-5066-bdba-dcd31a6d01ab + do + local ffi = require 'ffi' + + + local sha1_bin = ngx.sha1_bin + local C = ffi.C + local ffi_new = ffi.new + local ffi_str = ffi.string + local str_type = ffi.typeof('uint8_t[?]') + + ffi.cdef [[ + typedef unsigned char u_char; + u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len); + ]] + + + local function bin_tohex(s) + local len = #s * 2 + local buf = ffi_new(str_type, len) + C.ngx_hex_dump(buf, s, #s) + + return ffi_str(buf, len) + end + + + local function v5_hash(binary, name) + local hash = bin_tohex(sha1_bin(binary .. name)) + return hash, + tohex(bor(band(tonumber(sub(hash, 13, 14), 16), 0x0F), 0x50), 2), + tohex(bor(band(tonumber(sub(hash, 17, 18), 16), 0x3F), 0x80), 2) + end + + + function _M.factory_v5(namespace) + return factory(namespace, v5_hash) + end + end + + + --- Generate a v3 UUID. + -- v3 UUIDs are created from a namespace and a name (a UUID and a string). + -- The same name and namespace result in the same UUID. The same name and + -- different namespaces result in different UUIDs, and vice-versa. + -- The resulting UUID is derived using MD5 hashing. + -- + -- This is a sugar function which instanciates a short-lived v3 UUID factory. + -- It is an expensive operation, and intensive generation using the same + -- namespaces should prefer allocating their own long-lived factory with + -- `factory_v3`. + -- + -- @param [ t y p e = s t r i n g ] namespace (must be a valid UUID according to `is_valid`) + -- @param [ t y p e = s t r i n g ] name + -- @treturn string `uuid`: a v3 (namespaced) UUID. + -- @treturn string `err`: a string describing an error + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- local u = uuid.generate_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello') + -- --- > 3db7a435-8c56-359d-a563-1b69e6802c78 + function _M.generate_v3(namespace, name) + local fact, err = _M.factory_v3(namespace) + if not fact then + return nil, err + end + + return fact(name) + end + + + --- Generate a v5 UUID. + -- v5 UUIDs are created from a namespace and a name (a UUID and a string). + -- The same name and namespace result in the same UUID. The same name and + -- different namespaces result in different UUIDs, and vice-versa. + -- The resulting UUID is derived using SHA-1 hashing. + -- + -- This is a sugar function which instanciates a short-lived v5 UUID factory. + -- It is an expensive operation, and intensive generation using the same + -- namespaces should prefer allocating their own long-lived factory with + -- `factory_v5`. + -- + -- @param [ t y p e = s t r i n g ] namespace (must be a valid UUID according to `is_valid`) + -- @param [ t y p e = s t r i n g ] name + -- @treturn string `uuid`: a v5 (namespaced) UUID. + -- @treturn string `err`: a string describing an error + -- @usage + -- local uuid = require 'resty.jit-uuid' + -- + -- local u = uuid.generate_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello') + -- --- > 4850816f-1658-5890-8bfd-1ed14251f1f0 + function _M.generate_v5(namespace, name) + local fact, err = _M.factory_v5(namespace) + if not fact then + return nil, err + end + + return fact(name) + end + + else + function _M.factory_v3() error('v3 UUID generation only supported in ngx_lua', 2) end + + function _M.generate_v3() error('v3 UUID generation only supported in ngx_lua', 2) end + + function _M.factory_v5() error('v5 UUID generation only supported in ngx_lua', 2) end + + function _M.generate_v5() error('v5 UUID generation only supported in ngx_lua', 2) end + end +end + + +return setmetatable(_M, { + __call = _M.generate_v4 +}) diff --git a/orange/orange.lua b/orange/orange.lua index e5d6690e..b8dd1f03 100644 --- a/orange/orange.lua +++ b/orange/orange.lua @@ -3,6 +3,7 @@ local table_insert = table.insert local table_sort = table.sort local pcall = pcall local require = require +require("orange.lib.globalpatches")() local utils = require("orange.utils.utils") local config_loader = require("orange.utils.config_loader") local dao = require("orange.store.dao") @@ -82,6 +83,8 @@ function Orange.init(options) end function Orange.init_worker() + -- 仅在 init_worker 阶段调用,初始化随机因子,仅允许调用一次 + math.randomseed() -- 初始化定时器,清理计数器等 if Orange.data and Orange.data.store and Orange.data.config.store == "mysql" then local worker_id = ngx.worker.id() @@ -95,7 +98,7 @@ function Orange.init_worker() end end end, Orange.data.store, Orange.data.config) - + if not ok then ngx.log(ngx.ERR, "failed to create the timer: ", err) return os.exit(1) diff --git a/orange/utils/utils.lua b/orange/utils/utils.lua index 59707c90..4861d6e8 100644 --- a/orange/utils/utils.lua +++ b/orange/utils/utils.lua @@ -1,7 +1,7 @@ -- general utility functions. -- some functions is from [kong](getkong.org) local require = require -local uuid = require("orange.lib.uuid") +local uuid = require("orange.lib.jit-uuid") local date = require("orange.lib.date") local json = require("cjson") local type = type @@ -10,6 +10,17 @@ local pairs = pairs local tostring = tostring local string_gsub = string.gsub local string_find = string.find +local ffi = require "ffi" +local ffi_cdef = ffi.cdef +local ffi_typeof = ffi.typeof +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C + +ffi_cdef[[ +typedef unsigned char u_char; +int RAND_bytes(u_char *buf, int num); +]] local _M = {} @@ -29,7 +40,7 @@ function _M.current_timetable() local hour = day .. " " .. h local minute = hour .. ":" .. m local second = minute .. ":" .. s - + return { Day = day, Hour = hour, @@ -208,4 +219,24 @@ function _M.load_module_if_exists(module_name) end end +---Try to generate a random seed using OpenSSL. +-- ffi based, would be more effenticy +-- This function is mainly ispired by https://github.com/bungle/lua-resty-random +-- @return a pseudo-random number for math.randomseed +do + local bytes_buf_t = ffi_typeof "uint8_t[?]" + local n_bytes = 4 + function _M.get_random_seed() + local buf = ffi_new(bytes_buf_t, n_bytes) + + if C.RAND_bytes(buf, n_bytes) == 0 then + ngx.log(ngx.ERR, "could not get random bytes, using ngx.time() + ngx.worker.pid() instead") + return ngx.time() + ngx.worker.pid() + end + + local a, b, c, d = ffi_str(buf, n_bytes):byte(1, 4) + return a * 0x1000000 + b * 0x10000 + c * 0x100 + d + end +end + return _M