-
Notifications
You must be signed in to change notification settings - Fork 152
/
Copy pathreq.lua
153 lines (113 loc) · 3.61 KB
/
req.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
-- Copyright (C) Yichun Zhang (agentzh)
--
-- This library is an approximate Lua port of the standard ngx_limit_req
-- module.
local ffi = require "ffi"
local math = require "math"
local ngx_shared = ngx.shared
local ngx_now = ngx.now
local setmetatable = setmetatable
local ffi_cast = ffi.cast
local ffi_str = ffi.string
local abs = math.abs
local tonumber = tonumber
local type = type
local assert = assert
local max = math.max
-- TODO: we could avoid the tricky FFI cdata when lua_shared_dict supports
-- hash-typed values as in redis.
ffi.cdef[[
struct lua_resty_limit_req_rec {
unsigned long excess;
uint64_t last; /* time in milliseconds */
/* integer value, 1 corresponds to 0.001 r/s */
};
]]
local const_rec_ptr_type = ffi.typeof("const struct lua_resty_limit_req_rec*")
local rec_size = ffi.sizeof("struct lua_resty_limit_req_rec")
-- we can share the cdata here since we only need it temporarily for
-- serialization inside the shared dict:
local rec_cdata = ffi.new("struct lua_resty_limit_req_rec")
local _M = {
_VERSION = '0.06'
}
local mt = {
__index = _M
}
function _M.new(dict_name, rate, burst)
local dict = ngx_shared[dict_name]
if not dict then
return nil, "shared dict not found"
end
assert(rate > 0 and burst >= 0)
local self = {
dict = dict,
rate = rate * 1000,
burst = burst * 1000,
}
return setmetatable(self, mt)
end
-- sees an new incoming event
-- the "commit" argument controls whether should we record the event in shm.
-- FIXME we have a (small) race-condition window between dict:get() and
-- dict:set() across multiple nginx worker processes. The size of the
-- window is proportional to the number of workers.
function _M.incoming(self, key, commit)
local dict = self.dict
local rate = self.rate
local now = ngx_now() * 1000
local excess
-- it's important to anchor the string value for the read-only pointer
-- cdata:
local v = dict:get(key)
if v then
if type(v) ~= "string" or #v ~= rec_size then
return nil, "shdict abused by other users"
end
local rec = ffi_cast(const_rec_ptr_type, v)
local elapsed = now - tonumber(rec.last)
-- print("elapsed: ", elapsed, "ms")
-- we do not handle changing rate values specifically. the excess value
-- can get automatically adjusted by the following formula with new rate
-- values rather quickly anyway.
excess = max(tonumber(rec.excess) - rate * abs(elapsed) / 1000 + 1000,
0)
-- print("excess: ", excess)
if excess > self.burst then
return nil, "rejected"
end
else
excess = 0
end
if commit then
rec_cdata.excess = excess
rec_cdata.last = now
dict:set(key, ffi_str(rec_cdata, rec_size))
end
-- return the delay in seconds, as well as excess
return excess / rate, excess / 1000
end
function _M.uncommit(self, key)
assert(key)
local dict = self.dict
local v = dict:get(key)
if not v then
return nil, "not found"
end
if type(v) ~= "string" or #v ~= rec_size then
return nil, "shdict abused by other users"
end
local rec = ffi_cast(const_rec_ptr_type, v)
local excess = max(tonumber(rec.excess) - 1000, 0)
rec_cdata.excess = excess
rec_cdata.last = rec.last
dict:set(key, ffi_str(rec_cdata, rec_size))
return true
end
function _M.set_rate(self, rate)
self.rate = rate * 1000
end
function _M.set_burst(self, burst)
self.burst = burst * 1000
end
return _M