Intro to Lua and Openresty, Part 9: HTTP clients

Posted on March 9, 2017

In Part 8 of this series, we used lua to read from the Postgres database.

In this post, we’re going to cover the basics of HTTP clients in lua.

Rationale / Goals

Throughout this series, we’ve often used HTTP requests as part of running tests on the code in the exercise, but we’ve always used curl and the shell. In our demo, we’re going to run many thousands of messages through the system at a time, and to do so, we’re going to need to generate requests that are easily verifiable as being unique.

httpclient

We’ll use the httpclient library for these examples.

GET / POST

If you are following along in the code, we are here.

Here is our code:

cjson = require("cjson")
hc = require("httpclient").new()
resp = hc:post('http://127.0.0.1:8000/',
               cjson.encode({ hello = "world" }),
               {content_type = 'application/json'})
getr = hc:get('http://127.0.0.1:8000/list')
if resp.code == 200 then
  print("success!")
end
print(resp.body)
print(resp.code)
print(resp.headers)
print(resp.status_line)
print(resp.err)

print(getr.body)
print(getr.code)
print(getr.headers)
print(getr.status_line)
print(getr.err)

and our Dockerfile:

FROM openresty/openresty:alpine-fat

RUN apk add --update openssl-dev git
RUN /usr/local/openresty/luajit/bin/luarocks install luasocket
RUN /usr/local/openresty/luajit/bin/luarocks install luasec
RUN /usr/local/openresty/luajit/bin/luarocks install lua-cjson
RUN /usr/local/openresty/luajit/bin/luarocks install httpclient
ADD get-post.lua /src/get-post.lua
WORKDIR /src/
ENTRYPOINT /usr/local/openresty/luajit/bin/luajit
CMD ["get-post.lua"]

Batch POST

If you are following along in the code, we are here.

We want to generate “load” on our system. Our system takes a message via POST request and “processes” it. Let’s write a script to generate POST requests in a loop:

local cjson = require('cjson')
local cli   = require('cliargs')
local hc    = require('httpclient').new()
--
local default_url = 'http://127.0.0.1:8000'
local date_fmt    = '%m-%d-%Y--%H-%M-%S'
--
-- cli args, parsing, and help docs
cli:option('--limit=NUMBER', 'max number of posts to send', 100)
cli:option('--url=HTTP_URL', 'URL to POST messages to', default_url)
cli:flag('--list', 'hit the list endpoint to retrieve the latest posts', false)
--
-- auto-gen some JSON to send as a post/message
generate_post = function ()
  return { hello = world, now = os.date(date_fmt)}
end
-- where msg is some {}
send_post = function (url, msg)
  return hc:post(url,
                 cjson.encode(msg),
                 {content_type = 'application/json'})
end
-- hit the /list endpoint to retrieve the latest posts
get_posts = function (url)
  return hc:get(url)
end
-- our primary loop
batch_send = function (url, limit)
  while (limit > 0)
  do
    msg = generate_post()
    res = send_post(url, msg)
    if res.code == 200 then
      print('POST success! ' .. res.body)
    else
      print('non-200 return! ' .. res.code .. ' ' .. res.err)
    end
    limit = limit-1
  end
end
--
-- MAIN
--
local args, err = cli:parse()
-- if there's an error in parsing (or --help), print it!
if err then
  print(err)
  os.exit(0)
end
--
print('Let us post a bunch of messages to the service:')
i = math.random(0, args.limit)
print('We will print ' .. i .. ' messages this time..')
batch_send(args.url, i)


if args.list then
  print('Let us retrieve the top posts saved/written to the service:')
  posts = get_posts(args.url .. '/list').body
  print(posts)
end

Dockerfile

FROM openresty/openresty:alpine-fat

RUN apk add --update openssl-dev git
RUN /usr/local/openresty/luajit/bin/luarocks install luasocket
RUN /usr/local/openresty/luajit/bin/luarocks install luasec
RUN /usr/local/openresty/luajit/bin/luarocks install lua-cjson
RUN /usr/local/openresty/luajit/bin/luarocks install httpclient
RUN /usr/local/openresty/luajit/bin/luarocks install lua_cliargs
ADD batch-posts.lua /src/batch-posts.lua
WORKDIR /src/
ENTRYPOINT /usr/local/openresty/luajit/bin/luajit
CMD ["batch-posts.lua"]

Makefile

MAX ?= 1000

build:
	docker build --tag=load:11 --rm=true .

run:
	docker run --rm -it --net host --entrypoint /usr/local/openresty/luajit/bin/luajit load:11 batch-posts.lua --limit $(MAX)

dev:
	docker run --rm -it --net host -v `pwd`/batch-posts.lua:/src/batch-posts.lua --entrypoint /usr/local/openresty/luajit/bin/luajit load:11 batch-posts.lua --list

shell:
	docker run --rm -it --net host -v `pwd`/batch-posts.lua:/src/batch-posts.lua --entrypoint /bin/sh load:11

help:
	docker run --rm -it --entrypoint /usr/local/openresty/luajit/bin/luajit load:11 batch-posts.lua --help

Continuing on to Part 10, Pull it All Together.