-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathunitd.lua
More file actions
executable file
·377 lines (316 loc) · 10.7 KB
/
unitd.lua
File metadata and controls
executable file
·377 lines (316 loc) · 10.7 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#!/usr/bin/env lua5.4
local unit_dir = os.getenv('PWD')
local lib_dir = unit_dir .. '/lib'
local cfg_dir = unit_dir .. '/config'
local _VERSION = _VERSION
local ver = _VERSION:match('^Lua (.+)$')
local func, target_app = ...
func = func or 'info'
local require = require
local package = package
local assert = assert
local error = error
local pcall = pcall
local ipairs = ipairs
local pairs = pairs
local dofile = dofile
local print = print
local type = type
local next = next
local tonumber = tonumber
local tointeger = math.tointeger
local select = select
local collectgarbage = collectgarbage
local getenv = os.getenv
local exit = os.exit
local echo = io.write
local join = table.concat
local unpack = table.unpack
local _G = _G
-- NOTE hsq 只在 Lua5.4 限制并充分测试即可
-- setfenv(1, {})
local _ENV = {}
-- TODO hsq 共享模块加载路径,至少是基础模块。
-- NOTE hsq 相对路径易出问题。
package.path = join({
lib_dir .. '/?.lua',
cfg_dir .. '/?.lua',
unit_dir .. '/?.lua', -- 用于加载 App 入口
package.path,
}, ';')
package.cpath = join({
-- Sys, Nginx-Unit, Auxiliary module, ...
lib_dir .. '/'..ver..'/?.so',
package.cpath,
}, ';')
local config = require 'config'
local utils = require 'utils'
local unit = require 'lnginx-unit'
local cjson = require 'cjson'
local inspect = require 'inspect'
local jdec = cjson.decode
-- local jenc = cjson.encode
-- TODO hsq 用法还是繁琐;而且不方便注释掉;
-- TODO hsq 只在入口处导入 utils.base 。
local sh, is_jit, write_file, setcwd, pjson =
utils 'sh, is_jit, write_file, setcwd, pjson'
local app_configs = {}
-- gsub('.-:', '')
local SOCK = unit.DEFAULT_CONFIG.CONTROL_SOCK:match('^.-:?([^:]+)$')
local function pjson_encode(obj)
return pjson {
val = obj,
COMPACT = 2,
}
end
-- config.prepare()
for i, app in ipairs(config.apps) do
-- _G.USE_JIT = is_jit
-- _G.USE_JIT = app.use_jit
_G.app = app
-- TODO hsq 配置文件中也有加载路径处理,重复了;或者将其作为模块来加载?
-- NOTE hsq loadfile(nil) 卡住,需要检查参数非 nil 。
-- local config_data = assert(loadfile(assert(app.config_file)))()
local config_data = assert(dofile(assert(app.config_file)))
_G.app = nil
-- local config_data = `./$(app.config_file) -j -u`
if app.framework.name == 'vanilla' then
assert((config_data.vhost.applications[app.name].processes or 1) < 2,
'TODO hsq ngx.shared 模拟实现不支持多进程')
end
app.host, app.port = next(config_data.vhost.listeners):match('(.+):(.+)')
app.host = (app.host == '*') and 'localhost' or app.host
-- local config_str = jenc(config_data.vhost)
-- -- NOTE hsq unit 收到配置字串会自动过滤
-- config_str = config_str:gsub('\\/', '/')
app_configs[app.name] = config_data
if target_app == app.name or tonumber(target_app) == i then
target_app = app
end
end
assert(not target_app or type(target_app) == 'table', 'Invalid APP-NAME or APP-NO.')
local function echo_sh(cmd)
print(cmd)
print(sh(cmd))
end
local funcs = {}
local fks = {
-- 1:名, 2:App参数
{'info', 0},
{'restart',0},
{'start', 0},
{'quit', 0},
{'vhost', 1},
{'detail', 1},
{'update', 1},
{'get', 2},
}
local function list_apps()
print('apps:')
for i, app in ipairs(config.apps) do
print('', i, app.name)
end
end
function funcs.info()
print 'funcs:'
for _, c in ipairs(fks) do
local n, a = unpack(c)
local s, r = n:match('^(.)(.+)$')
local p = 'APP-NAME/No.'
echo(('\t\27[04m\27[01m%s\27[0m%s'):format(s, r))
print((a == 1 and ('\t[%s]'):format(p)) or
(a == 2 and ('\t<%s>'):format(p)) or '')
end
print ''
list_apps()
end
function funcs.detail(app)
if app then
local cfg = app_configs[app.name]
print('env:', inspect(app))
print('app:', inspect(cfg.app))
print('ngx:', inspect(cfg.ngx))
print('vhost:', pjson_encode(cfg.vhost))
else
print(inspect(config))
list_apps()
end
end
local function get_vhost(echo)
local cmd = ([[curl -s --unix-socket '%s' URL]]):format(SOCK)
if echo then
print(cmd)
end
local r = sh(cmd)
return (r and r ~= '') and jdec(r) or nil
end
function funcs.vhost(app)
-- echo_sh('cat '..unit.DEFAULT_CONFIG.STATE..'/conf.json')
local function prune(vhost, node)
if not vhost[node] then return end
for k, v in pairs(vhost[node]) do
if k ~= app.name or (node == 'listeners' and not v.pass:find(app.name)) then
vhost[node][k] = nil
end
end
end
local vhost = get_vhost(true)
if not vhost then
print ''
return
end
-- TODO hsq JSON 解码后是浮点数
for _, v in pairs((vhost.config or vhost).applications) do
v.processes = tointeger(v.processes)
end
if app then
-- print(inspect(app))
vhost = vhost.config or vhost
-- print(inspect(vhost))
prune(vhost, 'listeners')
prune(vhost, 'routes')
prune(vhost, 'applications')
-- print(inspect(vhost))
print(pjson_encode(vhost))
else
-- print(inspect(vhost))
print(pjson_encode(vhost))
list_apps()
end
end
function funcs.start() echo_sh('unitd') end
function funcs.quit() echo_sh('pkill unitd') end
function funcs.restart() funcs.quit() funcs.start() end
local empty_vhost = {applications={},routes={['']={}},listeners={}}
function funcs.update(app)
local function update_part(vhost, node)
local cmd = [[curl -s -XPUT --data-binary '%s' --unix-socket '%s' \
URL/config/%s/%s]]
local key, value = next(vhost[node])
cmd = cmd:format(pjson_encode(value), SOCK, node, key)
-- echo_sh(cmd)
print(cmd)
local r = sh(cmd)
return assert(jdec(r).success and r, r)
end
local function update(vhost, filepath, overwrite)
write_file(filepath, pjson_encode(vhost))
if overwrite then
local cmd = [[curl -s -X PUT --data-binary '%s' --unix-socket '%s' \
URL/config/]]
cmd = cmd:format('@' .. filepath, SOCK)
echo_sh(cmd)
else
local vhost0 = get_vhost()
vhost0 = vhost0 and (vhost0.config or vhost0)
if not vhost0 then
return update(vhost, filepath, true)
end
-- NOTE hsq 缺省没有 routes 节点: {"certificates": {},
-- "config": {"applications": {}, "listeners": {}}}
if not vhost0.routes then
update_part(empty_vhost, 'routes')
end
-- NOTE hsq 更新顺序根据依赖关系
-- NOTE hsq applications 重启 App 及其 Prototype 进程,routes/listeners 不会
local r
r = update_part(vhost, 'applications')
r = update_part(vhost, 'routes')
r = update_part(vhost, 'listeners')
print(r)
end
end
if app then
update(app_configs[app.name].vhost, app.vhost_file, false)
else
local vhost = {}
for _, a in ipairs(config.apps) do
for gk, gv in pairs(app_configs[a.name].vhost) do
local gvs = vhost[gk]
if not gvs then
gvs = {}
vhost[gk] = gvs
end
for k, v in pairs(gv) do
gvs[k] = v
end
end
end
update(vhost, cfg_dir .. '/config.json', true)
end
end
function funcs.get(app)
assert(app)
local path = '?desc=基于 Nginx/Unit 的 Lua 框架#'
-- local cmd = 'curl -s -H"cookie: a=b" "http://%s:%s/%s"'
local cmd = 'curl -s -b"a=b" "http://%s:%s/%s"'
echo_sh(cmd:format(app.host, app.port, path))
end
for _, c in ipairs(fks) do
funcs[c[1]:sub(1, 1)] = funcs[c[1]]
end
-- {{{ === === === === === === === === === === === === === === === === ===
-- TODO hsq 独立出来,共享配置处理?
local function unit_check(ret, rc, err)
return ret and ret or
error(('[%d]%s'):format(err or 'Failed!', err, rc), 2)
end
-- web 入口;其他方法是 shell 管理。
function funcs.run(app)
assert(getenv('NXT_UNIT_INIT'), 'Not executable in shell')
local app_entry = require('frameworks.' .. app.framework.name)
package.path = app.path
package.cpath = app.cpath
-- assert(setcwd(app.dir))
local app_config = app_configs[app.name]
_G.DEBUG = app_config.app.DEBUG
local lua_ver = utils.is_jit and (_G['jit'].version:match('^(.-)%-')) or _VERSION
local ngx_cfg = (require 'ngx.config')(app_config)
local make_ngx = require 'ngx'
local function get_ngx(req)
-- TODO hsq get_ngx 可共享、缓存 ngx 对象?
local ngx = make_ngx(ngx_cfg, req)
_G.ngx = ngx
return ngx
end
local prelude = nil
-- 测试
local function epilogue(ngx)
-- X-Powered-By 添加 Lua 版本信息
local xpb = ngx.header.x_powered_by
-- xpb = xpb and (xpb .. ' on ' .. lua_ver) or lua_ver
xpb = xpb and {xpb, lua_ver} or lua_ver
ngx.header.x_powered_by = xpb
end
local function protect_request_handler(req)
-- TODO hsq 请求之间共享 ngx ,内部状态需要清理!
-- TODO hsq 不要在模块中绑定 ngx : resty/template 。
-- _G.ngx = _G.ngx or get_ngx(req)
local ngx = get_ngx(req)
_G.ngx = ngx
if prelude then prelude(ngx) end
local ok, status = pcall(app_entry)
if not ok then
if type(status) == 'table' and status.from == 'ngx.exit' then
ok = true
status = status.status
else
ngx.log(ngx.ERR, status:gsub('\\([nt])', {n = '\n', t = '\t'}))
status = ngx.HTTP_INTERNAL_SERVER_ERROR
end
end
if epilogue then epilogue(ngx) end
local content = ngx.get_response_content()
local headers = ngx.get_response_headers()
return status or ngx.status, content, headers
end
local ctx = unit_check(unit.init(protect_request_handler))
unit.info(lua_ver)
collectgarbage('collect')
collectgarbage('collect')
unit_check(ctx:run())
ctx:done()
exit(true, true)
end
-- }}} === === === === === === === === === === === === === === === === ===
assert(funcs[func], 'Invalid function')(target_app, select(3, ...))