Skip to content

Interactive console (REPL) for Openresty to inspect Lua VM internals, to run lua code, to invoke functions and more

License

Notifications You must be signed in to change notification settings

nicoster/lua-resty-console

 
 

Repository files navigation

Welcome to Resty Console

Build Status

Aliaksandr's Resty Repl is a powerful tool with a bunch of nice features. I personally am a big fan of it. But still it has a few drawbacks:

  • Has to modify source code for each nginx handler in question to use Resty Repl
  • Nginx needs to run in non-daemon mode to leverage the TTY/readline facilities These limitations make it less useful for development workflow and production live debugging. So here comes the resty-console.

resty-console inherits the code of resty-repl so it inherits the features like:

  • readline full integration - all the shortcuts like ^A, ^R, ESC+DEL, .. are supported
  • auto completion / command history
  • pretty print objects (with inspect.lua)
  • and more ..

Status

Experimental.

Installation

Install resty-console with LuaRocks

luarocks install lua-resty-console

or get a feel of it with Docker

$ git clone https://github.com/nicoster/lua-resty-console.git
$ cd lua-resty-console
$ make demo   # invoking docker-compose
...
Connected to localhost:80
[1] ngx(content)> 

Synopsis

resty-console consists of 2 parts - backend and client. This separation allows you to enjoy all the goodies of readline without messing with the TTY thing in nginx process.

The client talks to the backend using REdis Serialization Protocol which is simple and efficient. Actually this design makes the client a universal REPL client with all the beloved readline features, as long as the implementation of a backend conforms to the protocol.

Backend

Add the following snippet into your nignx configuration

lua_shared_dict mycache 1M;   # for demo only 
lua_shared_dict metrics 1M;   # for demo only

server {
  listen 127.0.0.1:8001;
  location /console {
    content_by_lua_block {
      require('resty.console').start()
    }
  }

As this exposes Openresty Lua VM for inspection, and resty-console doesn't have builtin authentication yet, it's crucial to listen only on localhost ports. The location is hardcoded to /console in the client, and it's not configurable for now.

Client

  • Issue the following command to launch client and connect to the backend
$ luajit <LUAROCKS-DIR>/lib/resty/console/client.lua localhost:8001
Connected to localhost:8001. Press ^C twice to exit.
[1] ngx(content)>
  • Or run resty-cli localhost:8001 to connect.
    You may need to run PATH=$HOME/.luarocks/bin:$PATH first if your luarocks is installed with --local.

Auto Completion

[1] ngx(content)> ngx.E →→      #press tab twice
ngx.EMERG  ngx.ERR    ngx.ERROR        
[1] ngx(content)> _G. →→
_G._.                _G.ipairs()          _G.rawequal()
_G._G.               _G.jit.              _G.rawget()
_G._VERSION          _G.load()            _G.rawlen()
_G.__ngx_cycle       _G.loadfile()        _G.rawset()
_G.__ngx_req         _G.loadstring()      _G.require()
_G.assert()          _G.math.             _G.select()
_G.bit.              _G.module()          _G.setfenv()
_G.collectgarbage()  _G.ndk.              _G.setmetatable()
_G.coroutine.        _G.newproxy()        _G.string.
_G.debug.            _G.next()            _G.table.
_G.dofile()          _G.ngx.              _G.tonumber()
_G.error()           _G.os.               _G.tostring()
_G.gcinfo()          _G.package.          _G.type()
_G.getfenv()         _G.pairs()           _G.unpack()
_G.getmetatable()    _G.pcall()           _G.xpcall()
_G.io.               _G.print()         
[1] ngx(content)> _G.

Invoke Functions

[1] ngx(content)> f = function() return 'a', 'b' end
=> nil
[2] ngx(content)> f()
=> "a", "b"

[3] ngx(content)> ngx.md5('abc')
=> 900150983cd24fb0d6963f7d28e17f72
[4] ngx(content)> ngx.time()
=> 1547534019

Check VM Internals

Below is an example of replicating the functionalities of lua-resty-shdict-server, manipulating shared-dict with canonical APIs directly.

[9] ngx(content)> ngx.config.prefix()
=> /workspace/lua-resty-console/
[10] ngx(content)> ngx.config.ngx_lua_version
=> 10011
[11] ngx(content)> ngx.config.nginx_configure()
=>  --prefix=/usr/local/Cellar/openresty/1.13.6.1/nginx --with-cc-opt='-O2 -I/usr/local/include -I/usr/local/opt/pcre/include -I/usr/local/opt/openresty-openssl/include' --add-module=../ngx_devel_kit-0.3.0 --add-module=../echo-nginx-module-0.61 ...


[12] ngx(content)> ngx.sha →→
ngx.sha1_bin()  ngx.shared.     
[12] ngx(content)> ngx.shared. →→
ngx.shared.mycache.    ngx.shared.metrics.  
[12] ngx(content)> c = ngx.shared.mycache
=> nil
[13] ngx(content)> c
=> { <userdata 1>,
  <metatable> = <1>{
    __index = <table 1>,
    add = <function 1>,
    delete = <function 2>,
    flush_all = <function 3>,
    flush_expired = <function 4>,
    get = <function 5>,
    get_keys = <function 6>,
    get_stale = <function 7>,
    incr = <function 8>,
    llen = <function 9>,
    lpop = <function 10>,
    lpush = <function 11>,
    replace = <function 12>,
    rpop = <function 13>,
    rpush = <function 14>,
    safe_add = <function 15>,
    safe_set = <function 16>,
    set = <function 17>
  }
}
[14] ngx(content)> c:set('a', 1)
=> true
[15] ngx(content)> c:get('a')
=> 1
[16] ngx(content)> c:get_keys()
=> { "a" }

Hook Functions

Sometimes you want to check how a function has been invoked - like checking its return values. You can do it this way:

...
[18] ngx[2].content> u = require('lua.utils')
[19] ngx[2].content> u.verify_token
=> function: 0x16aa88c0
[20] ngx[2].content> u.verify_token2 = u.verify_token
=> nil
[21] ngx[2].content> u.verify_token2 
=> function: 0x16aa88c0
[22] ngx[2].content> u.verify_token = function(token) local result = u.verify_token2(token) ngx.log(ngx.DEBUG, 'result:', result) return result end
=> nil
[23] ngx[2].content> u.verify_token2 
=> function: 0x16aa88c0
[24] ngx[2].content> u.verify_token
=> function: 0x0ea75780
[25] ngx[2].content> u.verify_token('axb')
=> false
[26] ngx[2].content> 

You can check the error.logs and see something like:

2019/03/02 16:23:00 [debug] 73869#16875222: *810 [lua] [string "u.verify_token = function(token) local result = u..."]:1: result:false

Compatibility

Right now it's only compatible with:

  • Openresty/luajit

Todo

  • print support. print doesn't work at present as RESP doesn't support arbitrary length of data.
  • multi-line support
  • rewrite hiredis logic with luasocket

Known Issues

If nginx runs with multiple workers (which is the normal case), each worker has an isolated Lua VM to handle requests. When a resty-console client connects to the backend, there's a good chance that connections are established with different workers, thus each time the client is dealing with a different Lua VM.

There is a nginx patch listen per worker which might address this issue. It allows each worker to listen on a unique port, so client could connect to different workers at will.

OS Support

  • GNU/Linux
  • Mac OS

Credits

License

resty-console is released under the MIT License.

About

Interactive console (REPL) for Openresty to inspect Lua VM internals, to run lua code, to invoke functions and more

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Lua 93.6%
  • Makefile 2.8%
  • Shell 1.8%
  • Tcl 1.8%