Skip to content

Commit 93b9ce1

Browse files
committed
Initial commit
0 parents commit 93b9ce1

File tree

5 files changed

+406
-0
lines changed

5 files changed

+406
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 John Regan <john@jrjrtech.com>
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.

README.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
This requires your web server to have an active instance of
15+
[sockexec](https://github.com/jprjr/sockexec) running.
16+
17+
## Usage
18+
19+
```lua
20+
local exec = require'resty.exec'
21+
local prog = exec.new('/tmp/exec.sock')
22+
```
23+
24+
Creates a new `prog` object, using `/tmp/exec.sock` for its connection to
25+
sockexec.
26+
27+
From there, you can use `prog` in a couple of different ways:
28+
29+
### ez-mode
30+
31+
```lua
32+
local res, err = prog('uname')
33+
34+
-- res = { stdout = "Linux\n", stderr = nil, exitcode = 0, termsig = nil }
35+
-- err = nil
36+
37+
ngx.print(res.stdout)
38+
```
39+
40+
This will run `uname`, with no data on stdin.
41+
42+
Returns a table of output/error codes, with `err` set to any errors
43+
encountered.
44+
45+
### Setup argv beforehand
46+
47+
```lua
48+
prog.argv = { 'uname', '-a' }
49+
local res, err = prog()
50+
51+
-- 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 }
52+
-- err = nil
53+
54+
ngx.print(res.stdout)
55+
```
56+
57+
### Setup stdin beforehand
58+
59+
```lua
60+
prog.stdin = 'this is neat!'
61+
local res, err = prog('cat')
62+
63+
-- res = { stdout = "this is neat!", stderr = nil, exitcode = 0, termsig = nil }
64+
-- err = nil
65+
66+
ngx.print(res.stdout)
67+
```
68+
69+
### Call with explicit argv and stdin
70+
71+
```lua
72+
local res, err = prog( { argv = 'cat', stdin = 'fun!' } )
73+
74+
-- res = { stdout = "fun!", stderr = nil, exitcode = 0, termsig = nil }
75+
-- err = nil
76+
77+
ngx.print(res.stdout)
78+
```
79+
80+
Note: here `argv` is a string, which is fine if your program doesn't need any
81+
arguments.
82+
83+
### Setup stdout/stderr callbacks
84+
85+
If you set `prog.stdout` or `prog.stderr` to a function, it will be called for
86+
each chunk of stdout/stderr data received.
87+
88+
Please note that there's no guarantees of stdout/stderr being a complete
89+
string, or anything particularly sensible for that matter!
90+
91+
```lua
92+
-- set bufsize to some smaller value, the default is 4096
93+
-- this allows data to stream in smaller chunks
94+
prog.bufsize = 10
95+
96+
prog.stdout = function(data)
97+
ngx.print(data)
98+
ngx.flush(true)
99+
end
100+
101+
local res, err = prog('some-program')
102+
103+
```
104+
105+
### But I actually want a shell!
106+
107+
Not a problem! You can just do something like:
108+
109+
```lua
110+
local res, err = prog('bash','-c','echo $PATH')
111+
```
112+
113+
Or if you want to run an entire script:
114+
115+
```lua
116+
prog.stdin = script_data
117+
local res, err = prog('bash')
118+
```
119+
120+
121+
## Some example nginx configs
122+
123+
Assuming you're running sockexec at `/tmp/exec.sock`
124+
125+
```
126+
$ sockexec /tmp/exec.sock
127+
```
128+
129+
Then in your nginx config:
130+
131+
```nginx
132+
location /uname-1 {
133+
content_by_lua_block {
134+
local prog = require'resty.exec'.new('/tmp/exec.sock')
135+
local data,err = prog('uname')
136+
if(err) then
137+
ngx.say(err)
138+
else
139+
ngx.say(data.stdout)
140+
end
141+
}
142+
}
143+
location /uname-2 {
144+
content_by_lua_block {
145+
local prog = require'resty.exec'.new('/tmp/exec.sock')
146+
prog.argv = { 'uname', '-a' }
147+
local data,err = prog()
148+
if(err) then
149+
ngx.say(err)
150+
else
151+
ngx.say(data.stdout)
152+
end
153+
}
154+
}
155+
location /cat-1 {
156+
content_by_lua_block {
157+
local prog = require'resty.exec'.new('/tmp/exec.sock')
158+
prog.stdin = 'this is neat!'
159+
local data,err = prog('cat')
160+
if(err) then
161+
ngx.say(err)
162+
else
163+
ngx.say(data.stdout)
164+
end
165+
}
166+
}
167+
location /cat-2 {
168+
content_by_lua_block {
169+
local prog = require'resty.exec'.new('/tmp/exec.sock')
170+
local data,err = prog({argv = 'cat', stdin = 'awesome'})
171+
if(err) then
172+
ngx.say(err)
173+
else
174+
ngx.say(data.stdout)
175+
end
176+
}
177+
}
178+
location /slow-print {
179+
content_by_lua_block {
180+
local prog = require'resty.exec'.new('/tmp/exec.sock')
181+
prog.bufsize = 10
182+
prog.stdout = function(v)
183+
ngx.print(v)
184+
ngx.flush(true)
185+
end
186+
prog('/usr/local/bin/slow-print')
187+
}
188+
# look in `/misc` of this repo for `slow-print`
189+
}
190+
location /shell {
191+
content_by_lua_block {
192+
local prog = require'resty.exec'.new('/tmp/exec.sock')
193+
local data, err = prog('bash','-c','echo $PATH')
194+
if(err) then
195+
ngx.say(err)
196+
else
197+
ngx.say(data.stdout)
198+
end
199+
}
200+
}
201+
202+
```
203+
204+
## License
205+
206+
MIT license (see `LICENSE`)

lib/resty/exec.lua

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
local netstring = require'netstring'
2+
local pairs = pairs
3+
local tonumber = tonumber
4+
local unix
5+
6+
if ngx then
7+
unix = ngx.socket.tcp
8+
else
9+
unix = require'socket.unix'
10+
end
11+
12+
local _M = {
13+
_VERSION = '1.0.0'
14+
}
15+
16+
function _M.new(address)
17+
if not address then return nil, "must supply an address" end
18+
local a = address
19+
if ngx then
20+
a = 'unix:' .. a
21+
end
22+
local o = {
23+
argv = nil,
24+
stdin = nil,
25+
stdout = nil,
26+
stderr = nil,
27+
bufsize = 4096
28+
}
29+
30+
function o.exec(self,...)
31+
local args = {...}
32+
local c, err, ns, nserr, success, data, partial, curfield
33+
34+
local buffer = ""
35+
local ret = {stdout = "", stderr = "", exitcode = "", termsig = ""}
36+
37+
if #args > 0 then
38+
if type(args[1]) == "table" then
39+
if args[1].argv then
40+
if type(self.argv) == "table" then
41+
self.argv = args[1].argv
42+
else
43+
self.argv = { args[1].argv }
44+
end
45+
end
46+
if args[1].stdin then self.stdin = args[1].stdin end
47+
else
48+
self.argv = args
49+
end
50+
end
51+
52+
if not self then return nil, "missing parameter 'self'" end
53+
if not self.argv or #self.argv <= 0 then return nil, "no arguments supplied" end
54+
55+
ns, nserr = netstring.encode(#self.argv, self.argv, self.stdin, "")
56+
if nserr then
57+
err = ''
58+
for i in pairs(nserr) do
59+
if i > 1 then err = err .. '; ' end
60+
err = err .. nserr[i]
61+
end
62+
return nil, err
63+
end
64+
65+
c, err = unix()
66+
if err then return nil, err end
67+
68+
success, err = c:connect(a)
69+
if err then return nil, err end
70+
71+
c:send(ns)
72+
73+
while(not err) do
74+
data, err, partial = c:receive(self.bufsize)
75+
if err and err ~= "closed" then
76+
return nil, err
77+
end
78+
79+
if data then
80+
buffer = buffer .. data
81+
end
82+
83+
if partial then
84+
buffer = buffer .. partial
85+
end
86+
87+
if #buffer then
88+
local t, s = netstring.decode(buffer)
89+
buffer = s
90+
if t then
91+
for i,v in pairs(t) do
92+
if not curfield then curfield = v
93+
else
94+
if curfield == "stdout" then
95+
ret.stdout = ret.stdout .. v
96+
if self.stdout then
97+
self.stdout(v)
98+
end
99+
elseif curfield == "stderr" then
100+
ret.stderr = ret.stderr .. v
101+
if self.stderr then
102+
self.stderr(v)
103+
end
104+
elseif curfield == "exitcode" then
105+
ret.exitcode = ret.exitcode .. v
106+
elseif curfield == "termsig" then
107+
ret.termsig = ret.termsig .. v
108+
end
109+
curfield = nil
110+
end
111+
end
112+
end
113+
end
114+
end
115+
c:close()
116+
if #ret.exitcode then
117+
ret.exitcode = tonumber(ret.exitcode)
118+
else
119+
ret.exitcode = nil
120+
end
121+
if #ret.termsig then
122+
ret.termsig = tonumber(ret.termsig)
123+
else
124+
ret.termsig = nil
125+
end
126+
if not #ret.stdout then
127+
ret.stdout = nil
128+
end
129+
if not #ret.stderr then
130+
ret.stderr = nil
131+
end
132+
return ret, nil
133+
end
134+
135+
setmetatable(o,
136+
{ __call = function(...)
137+
local args = {...}
138+
return o.exec(unpack(args))
139+
end })
140+
141+
return o, nil
142+
end
143+
144+
return _M

misc/slow-print

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
}

0 commit comments

Comments
 (0)