├── LICENSE ├── README.md ├── dist.ini ├── lib └── resty │ ├── exec.lua │ └── exec │ └── socket.lua ├── misc └── slow-print └── rockspecs ├── lua-resty-exec-1.0.0-0.rockspec ├── lua-resty-exec-1.1.0-0.rockspec ├── lua-resty-exec-1.1.1-0.rockspec ├── lua-resty-exec-1.1.1-1.rockspec ├── lua-resty-exec-1.1.2-0.rockspec ├── lua-resty-exec-1.1.3-0.rockspec ├── lua-resty-exec-1.1.4-0.rockspec ├── lua-resty-exec-1.2.0-0.rockspec ├── lua-resty-exec-1.2.1-0.rockspec ├── lua-resty-exec-2.0.0-0.rockspec ├── lua-resty-exec-2.0.0-1.rockspec ├── lua-resty-exec-2.0.1-0.rockspec ├── lua-resty-exec-3.0.0-0.rockspec ├── lua-resty-exec-3.0.1-0.rockspec ├── lua-resty-exec-3.0.2-0.rockspec └── lua-resty-exec-3.0.3-0.rockspec /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 John Regan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-exec 2 | 3 | A small Lua module for executing processes. It's primarily 4 | intended to be used with OpenResty, but will work in regular Lua applications 5 | as well. When used with OpenResty, it's completely non-blocking (otherwise it 6 | falls back to using LuaSocket and does block). 7 | 8 | It's similar to (and inspired by) 9 | [lua-resty-shell](https://github.com/juce/lua-resty-shell), the primary 10 | difference being this module uses sockexec, which doesn't spawn a shell - 11 | instead you provide an array of argument strings, which means you don't need 12 | to worry about shell escaping/quoting/parsing rules. 13 | 14 | Additionally, as of version 2.0.0, you can use `resty.exec.socket` to access a 15 | lower-level interface that allows two-way communication with programs. You can 16 | read and write to running applications! 17 | 18 | This requires your web server to have an active instance of 19 | [sockexec](https://github.com/jprjr/sockexec) running. 20 | 21 | ## Changelog 22 | 23 | * `3.0.0` 24 | * new field returned: `unknown` - if this happens please send me a bug! 25 | * `2.0.0` 26 | * New `resty.exec.socket` module for using a duplex connection 27 | * `resty.exec` no longer uses the `bufsize` argument 28 | * `resty.exec` now accepts a `timeout` argument, specify in milliseconds, defaults to 60s 29 | * This is a major revision, please test thoroughly before upgrading! 30 | * No changelog before `2.0.0` 31 | 32 | ## Installation 33 | 34 | `lua-resty-exec` is available on [luarocks](https://luarocks.org/modules/jprjr/lua-resty-exec) 35 | as well as [opm](https://opm.openresty.org/), you can install it with `luarocks install 36 | lua-resty-exec` or `opm get jprjr/lua-resty-exec`. 37 | 38 | If you're using this outside of OpenResty, you'll also need the LuaSocket 39 | module installed, ie `luarocks install luasocket`. 40 | 41 | Additionally, you'll need `sockexec` running, see [its repo](https://github.com/jprjr/sockexec) 42 | for instructions. 43 | 44 | ## `resty.exec` Usage 45 | 46 | ```lua 47 | local exec = require'resty.exec' 48 | local prog = exec.new('/tmp/exec.sock') 49 | ``` 50 | 51 | Creates a new `prog` object, using `/tmp/exec.sock` for its connection to 52 | sockexec. 53 | 54 | From there, you can use `prog` in a couple of different ways: 55 | 56 | ### ez-mode 57 | 58 | ```lua 59 | local res, err = prog('uname') 60 | 61 | -- res = { stdout = "Linux\n", stderr = nil, exitcode = 0, termsig = nil } 62 | -- err = nil 63 | 64 | ngx.print(res.stdout) 65 | ``` 66 | 67 | This will run `uname`, with no data on stdin. 68 | 69 | Returns a table of output/error codes, with `err` set to any errors 70 | encountered. 71 | 72 | ### Setup argv beforehand 73 | 74 | ```lua 75 | prog.argv = { 'uname', '-a' } 76 | local res, err = prog() 77 | 78 | -- res = { stdout = "Linux localhost 3.10.18 #1 SMP Tue Aug 2 21:08:34 PDT 2016 x86_64 GNU/Linux\n", stderr = nil, exitcode = 0, termsig = nil } 79 | -- err = nil 80 | 81 | ngx.print(res.stdout) 82 | ``` 83 | 84 | ### Setup stdin beforehand 85 | 86 | ```lua 87 | prog.stdin = 'this is neat!' 88 | local res, err = prog('cat') 89 | 90 | -- res = { stdout = "this is neat!", stderr = nil, exitcode = 0, termsig = nil } 91 | -- err = nil 92 | 93 | ngx.print(res.stdout) 94 | ``` 95 | 96 | ### Call with explicit argv, stdin data, stdout/stderr callbacks 97 | 98 | ```lua 99 | local res, err = prog( { 100 | argv = 'cat', 101 | stdin = 'fun!', 102 | stdout = function(data) print(data) end, 103 | stderr = function(data) print("error:", data) end 104 | } ) 105 | 106 | -- res = { stdout = nil, stderr = nil, exitcode = 0, termsig = nil } 107 | -- err = nil 108 | -- 'fun!' is printed 109 | ``` 110 | 111 | Note: here `argv` is a string, which is fine if your program doesn't need 112 | any arguments. 113 | 114 | ### Setup stdout/stderr callbacks 115 | 116 | If you set `prog.stdout` or `prog.stderr` to a function, it will be called for 117 | each chunk of stdout/stderr data received. 118 | 119 | Please note that there's no guarantees of stdout/stderr being a complete 120 | string, or anything particularly sensible for that matter! 121 | 122 | ```lua 123 | prog.stdout = function(data) 124 | ngx.print(data) 125 | ngx.flush(true) 126 | end 127 | 128 | local res, err = prog('some-program') 129 | 130 | ``` 131 | 132 | ### Treat timeouts as non-errors 133 | 134 | By default, `sockexec` treats a timeout as an error. You can disable this by 135 | setting the object's `timeout_fatal` key to false. Examples: 136 | 137 | ```lua 138 | -- set timeout_fatal = false on the prog objects 139 | prog.timeout_fatal = false 140 | 141 | -- or, set it at calltime: 142 | local res, err = prog({argv = {'cat'}, timeout_fatal = false}) 143 | ``` 144 | 145 | ### But I actually want a shell! 146 | 147 | Not a problem! You can just do something like: 148 | 149 | ```lua 150 | local res, err = prog('bash','-c','echo $PATH') 151 | ``` 152 | 153 | Or if you want to run an entire script: 154 | 155 | ```lua 156 | prog.stdin = script_data 157 | local res, err = prog('bash') 158 | 159 | -- this is roughly equivalent to running `bash < script` on the CLI 160 | ``` 161 | 162 | ### Daemonizing processes 163 | 164 | I generally recommend against daemonizing processes - I think it's far 165 | better to use some kind of message queue and/or supervision system, so 166 | you can monitor processes, take actions on failure, and so on. 167 | 168 | That said, if you want to spin off some process, you could use 169 | `start-stop-daemon`, ie: 170 | 171 | ```lua 172 | local res, err = prog('start-stop-daemon','--pidfile','/dev/null','--background','--exec','/usr/bin/sleep', '--start','--','10') 173 | ``` 174 | 175 | will spawn `sleep 10` as a detached background process. 176 | 177 | If you don't want to deal with `start-stop-daemon`, I have a small utility 178 | for spawning a background program called [idgaf](https://github.com/jprjr/idgaf), ie: 179 | 180 | ```lua 181 | local res, err = prog('idgaf','sleep','10') 182 | ``` 183 | 184 | This will basically accomplish the same thing `start-stop-daemon` does without 185 | requiring a billion flags. 186 | 187 | ## `resty.exec.socket` Usage 188 | 189 | ```lua 190 | local exec_socket = require'resty.exec.socket' 191 | 192 | -- you can specify timeout in milliseconds, optional 193 | local client = exec_socket:new({ timeout = 60000 }) 194 | 195 | -- every new program instance requires a new 196 | -- call to connect 197 | local ok, err = client:connect('/tmp/exec.sock') 198 | 199 | -- send program arguments, only accepts a table of 200 | -- arguments 201 | client:send_args({'cat'}) 202 | 203 | -- send data for stdin 204 | client:send('hello there') 205 | 206 | -- receive data 207 | local data, typ, err = client:receive() 208 | 209 | -- `typ` can be one of: 210 | -- `stdout` - data from the program's stdout 211 | -- `stderr` - data from the program's stderr 212 | -- `exitcode` - the program's exit code 213 | -- `termsig` - if terminated via signal, what signal was used 214 | 215 | -- if `err` is set, data and typ will be nil 216 | -- common `err` values are `closed` and `timeout` 217 | print(string.format('Received %s data: %s',typ,data) 218 | -- will print 'Received stdout data: hello there' 219 | 220 | client:send('hey this cat process is still running') 221 | data, typ, err = client:receive() 222 | print(string.format('Received %s data: %s',typ,data) 223 | -- will print 'Received stdout data: hey this cat process is still running' 224 | 225 | client:send_close() -- closes stdin 226 | data, typ, err = client:receive() 227 | print(string.format('Received %s data: %s',typ,data) 228 | -- will print 'Received exitcode data: 0' 229 | 230 | data, typ, err = client:receive() 231 | print(err) -- will print 'closed' 232 | ``` 233 | 234 | ### `client` object methods: 235 | 236 | * **`ok, err = client:connect(path)`** 237 | 238 | Connects via unix socket to the path given. If this is running 239 | in nginx, the `unix:` string will be prepended automatically. 240 | 241 | * **`bytes, err = client:send_args(args)`** 242 | 243 | Sends a table of arguments to sockexec and starts the program. 244 | 245 | * **`bytes, err = client:send_data(data)`** 246 | 247 | Sends `data` to the program's standard input 248 | 249 | * **`bytes, err = client:send(data)`** 250 | 251 | Just a shortcut to `client:send_data(data)` 252 | 253 | * **`bytes, err = client:send_close()`** 254 | 255 | Closes the program's standard input. You can also send an empty 256 | string, like `client:send_data('')` 257 | 258 | * **`data, typ, err = client:receive()`** 259 | 260 | Receives data from the running process. `typ` indicates the type 261 | of data, which can be `stdout`, `stderr`, `termsig`, `exitcode` 262 | 263 | `err` is typically either `closed` or `timeout` 264 | 265 | * **`client:close()`** 266 | 267 | Forcefully closes the client connection 268 | 269 | * **`client:getfd()`** 270 | 271 | A getfd method, useful if you want to monitor the underlying socket 272 | connection in a select loop 273 | 274 | ## Some example nginx configs 275 | 276 | Assuming you're running sockexec at `/tmp/exec.sock` 277 | 278 | ``` 279 | $ sockexec /tmp/exec.sock 280 | ``` 281 | 282 | Then in your nginx config: 283 | 284 | ```nginx 285 | location /uname-1 { 286 | content_by_lua_block { 287 | local prog = require'resty.exec'.new('/tmp/exec.sock') 288 | local data,err = prog('uname') 289 | if(err) then 290 | ngx.say(err) 291 | else 292 | ngx.say(data.stdout) 293 | end 294 | } 295 | } 296 | location /uname-2 { 297 | content_by_lua_block { 298 | local prog = require'resty.exec'.new('/tmp/exec.sock') 299 | prog.argv = { 'uname', '-a' } 300 | local data,err = prog() 301 | if(err) then 302 | ngx.say(err) 303 | else 304 | ngx.say(data.stdout) 305 | end 306 | } 307 | } 308 | location /cat-1 { 309 | content_by_lua_block { 310 | local prog = require'resty.exec'.new('/tmp/exec.sock') 311 | prog.stdin = 'this is neat!' 312 | local data,err = prog('cat') 313 | if(err) then 314 | ngx.say(err) 315 | else 316 | ngx.say(data.stdout) 317 | end 318 | } 319 | } 320 | location /cat-2 { 321 | content_by_lua_block { 322 | local prog = require'resty.exec'.new('/tmp/exec.sock') 323 | local data,err = prog({argv = 'cat', stdin = 'awesome'}) 324 | if(err) then 325 | ngx.say(err) 326 | else 327 | ngx.say(data.stdout) 328 | end 329 | } 330 | } 331 | location /slow-print { 332 | content_by_lua_block { 333 | local prog = require'resty.exec'.new('/tmp/exec.sock') 334 | prog.stdout = function(v) 335 | ngx.print(v) 336 | ngx.flush(true) 337 | end 338 | prog('/usr/local/bin/slow-print') 339 | } 340 | # look in `/misc` of this repo for `slow-print` 341 | } 342 | location /shell { 343 | content_by_lua_block { 344 | local prog = require'resty.exec'.new('/tmp/exec.sock') 345 | local data, err = prog('bash','-c','echo $PATH') 346 | if(err) then 347 | ngx.say(err) 348 | else 349 | ngx.say(data.stdout) 350 | end 351 | } 352 | } 353 | 354 | ``` 355 | 356 | ## License 357 | 358 | MIT license (see `LICENSE`) 359 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-exec 2 | version = 3.0.3 3 | abstract = Run external programs in OpenResty without spawning a shell or blocking 4 | author = John Regan 5 | is_original = yes 6 | license = mit 7 | repo_link = https://github.com/jprjr/lua-resty-exec 8 | lib_dir = lib 9 | doc_dir = lib 10 | main_module = lib/resty/exec.lua 11 | requires = luajit, jprjr/netstring >= 1.0.6 12 | -------------------------------------------------------------------------------- /lib/resty/exec.lua: -------------------------------------------------------------------------------- 1 | local exec = require"resty.exec.socket" 2 | local tonumber = tonumber 3 | local table_unpack = unpack or table.unpack -- luacheck: compat 4 | local insert = table.insert 5 | local concat = table.concat 6 | 7 | local _M = { 8 | _VERSION = "3.0.3" 9 | } 10 | 11 | function _M.new(address) 12 | if not address then return nil, "must supply an address" end 13 | 14 | local o = { 15 | argv = nil, 16 | stdin = nil, 17 | stdout = nil, 18 | stderr = nil, 19 | unknown = nil, 20 | timeout = 60000, 21 | timeout_fatal = true, 22 | } 23 | 24 | function o.exec(self,...) 25 | local args = {...} 26 | local c, err, _ 27 | 28 | local ret = {unknown = {}, stdout = {}, stderr = {}, exitcode = "", termsig = ""} 29 | 30 | if #args > 0 then 31 | if type(args[1]) == "table" then 32 | if args[1].argv then 33 | if type(args[1].argv) == "table" then 34 | self.argv = args[1].argv 35 | else 36 | self.argv = { args[1].argv } 37 | end 38 | end 39 | if args[1].stdin then self.stdin = args[1].stdin end 40 | if args[1].timeout_fatal ~= nil then self.timeout_fatal = args[1].timeout_fatal end 41 | 42 | if args[1].stdout then 43 | if type(args[1].stdout) == "function" then 44 | self.stdout = args[1].stdout 45 | else 46 | return nil, "invalid argument 'stdout' (requires function)" 47 | end 48 | end 49 | 50 | if args[1].stderr then 51 | if type(args[1].stderr) == "function" then 52 | self.stderr = args[1].stderr 53 | else 54 | return nil, "invalid argument 'stderr' (requires function)" 55 | end 56 | end 57 | 58 | if args[1].unknown then 59 | if type(args[1].unknown) == "function" then 60 | self.unknown = args[1].unknown 61 | else 62 | return nil, "invalid argument 'unknown' (requires function)" 63 | end 64 | end 65 | 66 | if args[1].timeout then 67 | self.timeout = args[1].timeout 68 | end 69 | 70 | else 71 | self.argv = args 72 | end 73 | end 74 | 75 | if not self then return nil, "missing parameter 'self'" end 76 | if not self.argv or #self.argv <= 0 then return nil, "no arguments supplied" end 77 | 78 | local cbs = { 79 | ["unknown"] = function(v) 80 | if self.unknown then 81 | self.unknown(v) 82 | else 83 | insert(ret.unknown,v) 84 | end 85 | end, 86 | ["stdout"] = function(v) 87 | if self.stdout then 88 | self.stdout(v) 89 | else 90 | insert(ret.stdout,v) 91 | end 92 | end, 93 | ["stderr"] = function(v) 94 | if self.stderr then 95 | self.stderr(v) 96 | else 97 | insert(ret.stderr,v) 98 | end 99 | end, 100 | ["termsig"] = function(v) 101 | ret.termsig = ret.termsig .. v 102 | end, 103 | ["exitcode"] = function(v) 104 | ret.exitcode = ret.exitcode .. v 105 | end, 106 | } 107 | 108 | c,err = exec:new({timeout = self.timeout}) 109 | if err then return nil, err end 110 | _, err = c:connect(address) 111 | if err then return nil, err end 112 | 113 | c:send_args(self.argv) 114 | if self.stdin then c:send(self.stdin) end 115 | c:send_close() 116 | err = nil 117 | 118 | while(not err) do 119 | local data, typ 120 | data, typ, err = c:receive() 121 | 122 | if err then 123 | if err == "timeout" then 124 | if self.timeout_fatal then 125 | return nil, err 126 | else 127 | err = nil 128 | end 129 | else 130 | if err ~= "timeout" and err ~= "closed" then 131 | return nil, err 132 | end 133 | end 134 | end 135 | 136 | if typ then 137 | cbs[typ](data) 138 | end 139 | end 140 | c:close() 141 | 142 | if #ret.exitcode then 143 | ret.exitcode = tonumber(ret.exitcode) 144 | else 145 | ret.exitcode = nil 146 | end 147 | if #ret.termsig then 148 | ret.termsig = tonumber(ret.termsig) 149 | else 150 | ret.termsig = nil 151 | end 152 | if not #ret.stdout then 153 | ret.stdout = nil 154 | else 155 | ret.stdout = concat(ret.stdout,'') 156 | end 157 | if not #ret.stderr then 158 | ret.stderr = nil 159 | else 160 | ret.stderr = concat(ret.stderr,'') 161 | end 162 | if not #ret.unknown then 163 | ret.unknown = nil 164 | else 165 | ret.unknown = concat(ret.unknown,'') 166 | end 167 | return ret, nil 168 | end 169 | 170 | setmetatable(o, 171 | { __call = function(...) 172 | local args = {...} 173 | return o.exec(table_unpack(args)) 174 | end }) 175 | 176 | return o, nil 177 | end 178 | 179 | return _M 180 | -------------------------------------------------------------------------------- /lib/resty/exec/socket.lua: -------------------------------------------------------------------------------- 1 | local netstring = require'netstring' 2 | local pairs = pairs 3 | local ngx = ngx or false -- luacheck: ignore 4 | local unix, socket 5 | local string_len = string.len 6 | local insert = table.insert 7 | local sub = string.sub 8 | local byte = string.byte 9 | 10 | if ngx then -- luacheck: ignore 11 | unix = ngx.socket.tcp -- luacheck: ignore 12 | else 13 | socket = require'socket' 14 | unix = require'socket.unix' 15 | end 16 | 17 | local _M = { 18 | _VERSION = '3.0.3' 19 | } 20 | local mt = { __index = _M } 21 | 22 | local function ns_encode(...) 23 | local ns, nserr = netstring.encode(unpack({...})) 24 | if nserr then 25 | local err = '' 26 | for i in pairs(nserr) do 27 | if i> 1 then err = err .. '; ' end 28 | err = err .. nserr[i] 29 | end 30 | return nil, err 31 | end 32 | return ns, nil 33 | end 34 | 35 | local function ns_send(sock,...) 36 | local ns, nserr = ns_encode(unpack({...})) 37 | if nserr then 38 | return nil, 'netstring: ' .. nserr 39 | end 40 | 41 | local bytes, err = sock:send(ns) 42 | if err then 43 | return nil, 'socket: ' .. err 44 | end 45 | return bytes, nil 46 | end 47 | 48 | -- returns data, typ, newindex 49 | -- newindex == -1 indicates all pairs are processed 50 | local function ns_pairs_shift(ns_pairs,ns_pairs_index) 51 | local data, typ 52 | local index = ns_pairs_index 53 | if (#ns_pairs - index > 1) then 54 | data = ns_pairs[index + 2] 55 | typ = ns_pairs[index + 1] 56 | 57 | index = index + 2 58 | 59 | if index == #ns_pairs then 60 | index = -1 61 | end 62 | end 63 | return data, typ, index 64 | end 65 | 66 | local function grab_ns(self, timeout) 67 | if self.bufsize == -1 then 68 | local t 69 | repeat 70 | if not ngx then -- luacheck: ignore 71 | local _, _, sockerr = socket.select({self.sock},nil,timeout) 72 | 73 | if sockerr then 74 | return nil, sockerr 75 | end 76 | self.sock:settimeout(0) 77 | else 78 | if timeout == 0 then 79 | timeout = 1 80 | end 81 | self.sock:settimeout(timeout) 82 | end 83 | 84 | local dat, err, partial = self.sock:receive(1) 85 | 86 | if err == 'closed' then 87 | self.closed = true 88 | self.bufsize = -1 89 | self.bufsize_rem = 0 90 | self.bufsize_cur = 0 91 | self.chunk = '' 92 | return nil, 'closed' 93 | end 94 | 95 | if not ngx then 96 | self.sock:settimeout(timeout) 97 | end 98 | 99 | if partial then 100 | dat = partial 101 | end 102 | 103 | if not dat or string_len(dat) <= 0 then return nil, err end 104 | 105 | t = byte(dat,1) - 48 106 | if(t >= 0 and t <= 9) then 107 | self.bufsize_cur = (self.bufsize_cur * 10) + t 108 | end 109 | until( t<0 or t>9 ) 110 | 111 | if t == 10 then -- colon = 58, then -48 from above loop 112 | self.bufsize = self.bufsize_cur + 1 113 | self.bufsize_rem = self.bufsize_cur + 1 114 | else 115 | self.bufsize = -1 116 | self.bufsize_rem = 0 117 | self.chunk = '' 118 | self.sock:close() 119 | return nil, 'error: netstring violated' 120 | end 121 | end 122 | 123 | self.bufsize_cur = 0 124 | 125 | while (self.bufsize_rem > 0) do 126 | local b = self.bufsize_rem > 8192 and 8192 or self.bufsize_rem 127 | 128 | if not ngx then -- luacheck: ignore 129 | local _, _, sockerr = socket.select({self.sock},nil,timeout) 130 | 131 | if sockerr then 132 | return nil, sockerr 133 | end 134 | self.sock:settimeout(0) 135 | else 136 | self.sock:settimeout(timeout) 137 | end 138 | 139 | local dat, err, partial = self.sock:receive(b) 140 | 141 | if not ngx then 142 | self.sock:settimeout(timeout) 143 | end 144 | 145 | if partial then 146 | dat = partial 147 | end 148 | 149 | if not dat then return nil, nil, err end 150 | self.chunk = self.chunk .. dat 151 | self.bufsize_rem = self.bufsize_rem - b 152 | end 153 | 154 | if(byte(self.chunk,self.bufsize) ~= 44) then 155 | self.bufsize = -1 156 | self.chunk = '' 157 | self.bufsize_cur = 0 158 | self.sock:close() 159 | return nil, 'error: netstring violated' 160 | end 161 | 162 | insert(self.ns_pairs,sub(self.chunk,1,self.bufsize-1)) 163 | self.bufsize = -1 164 | self.chunk = '' 165 | 166 | return true, nil 167 | end 168 | 169 | function _M.new(self, opts) -- luacheck: ignore 170 | local sock, err = unix() 171 | 172 | if not sock then 173 | return nil, err 174 | end 175 | 176 | local timeout = 60000 177 | 178 | if opts then 179 | if opts.timeout then 180 | timeout = opts.timeout 181 | end 182 | end 183 | 184 | if not ngx then 185 | timeout = timeout / 1000 186 | end 187 | 188 | sock:settimeout(timeout) 189 | 190 | return setmetatable({ 191 | sock = sock, 192 | timeout = timeout, 193 | args_sent = false, 194 | bufsize = -1, 195 | bufsize_cur = 0, 196 | chunk = '', 197 | ns_pairs = {}, 198 | ns_pairs_index = 0, 199 | closed = true, 200 | }, mt) 201 | 202 | end 203 | 204 | function _M.connect(self, uri) 205 | if not self.sock then 206 | return nil, 'user_error: not setup' 207 | end 208 | 209 | local u = uri 210 | 211 | if ngx then -- luacheck: ignore 212 | u = 'unix:' .. u 213 | end 214 | 215 | local ok, err = self.sock:connect(u) 216 | if not ok then 217 | return nil, err 218 | end 219 | self.closed = false 220 | 221 | return true, nil 222 | end 223 | 224 | function _M.send_args(self, args) 225 | if self.args_sent then 226 | return nil, 'user_error: args already sent' 227 | end 228 | 229 | if(self.closed) then 230 | return nil, 'user_error: connection closed' 231 | end 232 | 233 | if(type(args)) ~= 'table' or #args < 1 then 234 | return nil, 'user_error: args must be array-like table' 235 | end 236 | 237 | local bytes, err = ns_send(self.sock,#args, args) 238 | if err then 239 | return nil, err 240 | end 241 | 242 | self.args_sent = true 243 | return bytes, nil 244 | end 245 | 246 | function _M.send_data(self, data) 247 | if(self.closed) then 248 | return nil, 'user_error: connection closed' 249 | end 250 | if not self.args_sent then 251 | return nil, 'user_error: must send args first' 252 | end 253 | 254 | local bytes, err = ns_send(self.sock, data) 255 | if err then 256 | return nil, err 257 | end 258 | 259 | return bytes, nil 260 | end 261 | 262 | function _M.send_close(self) 263 | return _M.send_data(self, '') 264 | end 265 | 266 | function _M.send(self, data) 267 | return _M.send_data(self, data) 268 | end 269 | 270 | function _M.receive(self) 271 | if not self.sock then 272 | return nil, 'user_error: socket not created' 273 | end 274 | 275 | if not self.args_sent then 276 | return nil, 'user_error: must send args first' 277 | end 278 | 279 | if self.closed and #self.ns_pairs == 0 then 280 | return nil, nil, 'closed' 281 | end 282 | 283 | local data, typ, index 284 | 285 | data, typ, index = ns_pairs_shift(self.ns_pairs,self.ns_pairs_index) 286 | 287 | if index == -1 then 288 | self.ns_pairs = {} 289 | self.ns_pairs_index = 0 290 | else 291 | self.ns_pairs_index = index 292 | end 293 | 294 | if data then 295 | return data, typ, nil 296 | end 297 | 298 | local read_ns = true 299 | local attempt = 0 300 | while read_ns do 301 | local timeout = attempt == 0 and self.timeout or 0 302 | attempt = attempt + 1 303 | local ok, _ = grab_ns(self,timeout) 304 | if not ok then 305 | read_ns = false 306 | end 307 | end 308 | 309 | data, typ, index = ns_pairs_shift(self.ns_pairs,self.ns_pairs_index) 310 | if index == -1 then 311 | self.ns_pairs = {} 312 | self.ns_pairs_index = 0 313 | else 314 | self.ns_pairs_index = index 315 | end 316 | 317 | if data then 318 | return data, typ, nil 319 | end 320 | 321 | return nil, nil, 'timeout' 322 | end 323 | 324 | function _M.close(self) 325 | if not self.sock then 326 | return nil, 'user:error: socket not setup' 327 | end 328 | if not self.closed then 329 | self.closed = true 330 | end 331 | return self.sock:close() 332 | end 333 | 334 | function _M.getfd(self) 335 | if not self.sock then 336 | return nil, 'user_error: socket not setup' 337 | end 338 | return self.sock:getfd() 339 | end 340 | 341 | return _M 342 | -------------------------------------------------------------------------------- /misc/slow-print: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | select(STDERR); 7 | $| = 1; 8 | select(STDOUT); 9 | $| = 1; 10 | 11 | foreach my $i (0..10) { 12 | print("Line $i\n"); 13 | sleep(2); 14 | } 15 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.0.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.0.0-0" 3 | source = { 4 | url = "git://github.com/jprjr/lua-resty-exec.git", 5 | tag = "1.0.0" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.0-0" 3 | source = { 4 | url = "git://github.com/jprjr/lua-resty-exec.git", 5 | tag = "1.1.0" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.1-0" 3 | source = { 4 | url = "git://github.com/jprjr/lua-resty-exec.git", 5 | tag = "1.1.1" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.1-1" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.1.1.tar.gz", 5 | file = "lua-resty-exec-1.1.1.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.2-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.1.2.tar.gz", 5 | file = "lua-resty-exec-1.1.2.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.3-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.3-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.1.3.tar.gz", 5 | file = "lua-resty-exec-1.1.3.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.1.4-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.1.4-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.1.4.tar.gz", 5 | file = "lua-resty-exec-1.1.4.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.2.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.2.0-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.2.0.tar.gz", 5 | file = "lua-resty-exec-1.2.0.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-1.2.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "1.2.1-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/1.2.1.tar.gz", 5 | file = "lua-resty-exec-1.2.1.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua" 16 | } 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | "netstring >= 1.0.4" 21 | } 22 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-2.0.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "2.0.0-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/2.0.0.tar.gz", 5 | file = "lua-resty-exec-2.0.0.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-2.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "2.0.0-1" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/2.0.0.tar.gz", 5 | file = "lua-resty-exec-2.0.0.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-2.0.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "2.0.1-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/2.0.1.tar.gz", 5 | file = "lua-resty-exec-2.0.1.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-3.0.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "3.0.0-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/3.0.0.tar.gz", 5 | file = "lua-resty-exec-3.0.0.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-3.0.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "3.0.1-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/3.0.1.tar.gz", 5 | file = "lua-resty-exec-3.0.1.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-3.0.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "3.0.2-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/3.0.2.tar.gz", 5 | file = "lua-resty-exec-3.0.2.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-exec-3.0.3-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-exec" 2 | version = "3.0.3-0" 3 | source = { 4 | url = "https://github.com/jprjr/lua-resty-exec/archive/3.0.3.tar.gz", 5 | file = "lua-resty-exec-3.0.3.tar.gz" 6 | } 7 | description = { 8 | summary = "Run external programs in OpenResty without spawning a shell", 9 | homepage = "https://github.com/jprjr/lua-resty-exec", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.exec"] = "lib/resty/exec.lua", 16 | ["resty.exec.socket"] = "lib/resty/exec/socket.lua", 17 | } 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "netstring >= 1.0.6" 22 | } 23 | --------------------------------------------------------------------------------