Intro to Lua and Openresty, Part 1: Hello World Examples
This post is part one of my “Intro to Lua and Openresty” series. For more info on the series (including scope, research notes, and the usual disclaimers), see the introductory post.
Execution environment
While there are a few ways you can run Lua code, we want to minimize the setup and administrative overhead. Docker helps keep the localhost clean, and the openresty
images are a great way to get a fully functional lua environment. This set of images would also serve as a great base image when we get to building our own (later in the series). These images include luajit
, which can run arbitrary lua code as an interpreted script. They also provide several flavors of the image, including a release on Alpine. Lua distributes modules with luarocks
(package manager), and this is available on the alpine-fat
flavor.
Hello World! in Lua
The canonical Hello World!
in Lua is ridiculously simple:
ᐅ docker run -it --entrypoint /bin/sh openresty/openresty:alpine
/ # echo 'print("hello world!")' > hello.lua
/ #
/ # /usr/local/openresty/luajit/bin/luajit hello.lua
hello world!
So simple in fact, I skipped including it in the git repo with example code for the series. But there you have it :)
Hello World! in Openresty
OK, with that out of the way, let’s get into Openresty! We will start with the HTML version, and then do one with JSON.
the HTML Version
For these examples, we use content_by_lua
to keep it simple and embed the lua code directly into nginx.conf
:
worker_processes 1;
error_log error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8000;
location / {
default_type text/html;
content_by_lua '
ngx.say("<p>hello world!</p>")
';
}
}
}
ngx.say
is provided by openresty and available in the nginx environment by default, so there is nothing to “import”.
If you are following along with the git repo, find the example here.
Let’s run it with the openresty:alpine
docker image. We’ll use --volume
to “mount” the nginx.conf
into the container:
ᐅ docker run --name lua --rm --volume `pwd`:/usr/local/openresty/nginx/conf/ -p 127.0.0.1:8000:8000 openresty/openresty:alpine
nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful
Let’s check it out:
ᐅ curl localhost:8000
<p>hello world!</p>
Yay!
Hello World - JSON Version
The nginx.conf
this time…
worker_processes 1;
error_log error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8000;
charset utf-8;
charset_types application/json;
default_type application/json;
location / {
content_by_lua '
local cjson = require "cjson"
ngx.status = ngx.HTTP_OK
ngx.say(cjson.encode({ status = true, foobar = "string" }))
return ngx.exit(ngx.HTTP_OK)
';
}
}
}
Break down the Code
If you are following along with the git repo, find the example here.
Note that we’ve updated the content type spec to application/json
. This is an nginx thing. It is nice that Openresty builds on the standard Nginx config directives.
This example uses content_by_lua
to embed the lua code directly in the nginx.conf
. There is also a directive that can load the lua code from a file, but for simplicity and clarity, the examples in this series will keep the code in nginx.conf
.
cjson
is a lua module for encoding and decoding JSON data. There are a couple other options, but this is included in openresty, so there is no need to install a module with luarocks
(we’ll get there in time).
local cjson = require "cjson"
is an import of sorts. You may also see examples that use require("cjson")
instead. I am not sure which is more common, and it seems to be an arbitrary stylistic detail.
The JSON data we return in this example is { status = true, foobar = "string" }
. Note the use of "
for the string, while true
is recognized as a boolean type. More info on cjson
can be found in the lua-cjson manual.
Let’s see it run!
We use the same openresty docker image to run the JSON hello world:
ᐅ cd examples/02-hello-world-json
ᐅ docker run --name lua --rm --volume `pwd`:/usr/local/openresty/nginx/conf/ -p 127.0.0.1:8000:8000 openresty/openresty:alpine
Check it out…
ᐅ curl -i localhost:8000
HTTP/1.1 200 OK
Server: openresty/1.11.2.2
Date: Thu, 02 Mar 2017 15:43:20 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":true,"foobar":"string"}
Drop into the docker container to debug…
If you need to debug, it’s nice to see what’s in the error log, for example:
ᐅ docker exec -it lua /bin/sh
/ #
/ # cat /usr/local/openresty/nginx/error.log
2017/03/02 15:40:13 [error] 5#5: *1 lua entry thread aborted: runtime error: content_by_lua(nginx.conf:18):3: attempt to index global 'cjson' (a nil value)
stack traceback:
coroutine 0:
content_by_lua(nginx.conf:18): in function <content_by_lua(nginx.conf:18):1>, client: 172.17.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8000"
If you change the nginx.conf
while Nginx is running, you can reload nginx
to refresh the config in memory:
ᐅ docker exec -it lua /usr/local/openresty/nginx/sbin/nginx -s reload
2017/03/02 15:47:19 [notice] 7#7: signal process started
Makefile
We can combine the shell commands above into a Makefile
to establish and codify a workflow for development and testing to add or modify code, eg:
run:
docker run --name lua --rm --volume `pwd`:/usr/local/openresty/nginx/conf/ -p 127.0.0.1:8000:8000 openresty/openresty:alpine
reload:
docker exec -it lua /usr/local/openresty/nginx/sbin/nginx -s reload
error-logs:
docker exec -it lua cat /usr/local/openresty/nginx/error.log
logs:
docker logs lua
get:
curl -i localhost:8000
Using these make targets would look like:
ᐅ make run
docker run --name lua --rm --volume `pwd`:/usr/local/openresty/nginx/conf/ -p 127.0.0.1:8000:8000 openresty/openresty:alpine
ᐅ make get
curl -i localhost:8000
HTTP/1.1 200 OK
Server: openresty/1.11.2.2
Date: Thu, 23 Mar 2017 07:16:24 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":true,"foobar":"string"}
ᐅ make logs
docker logs lua
172.17.0.1 - - [23/Mar/2017:07:16:24 +0000] "GET / HTTP/1.1" 200 45 "-" "curl/7.35.0"
ᐅ make reload
docker exec -it lua /usr/local/openresty/nginx/sbin/nginx -s reload
2017/03/23 07:17:20 [notice] 10#10: signal process started
Next Steps
Continue on to Part 2: JSON/POST Processing in Openresty.