HTTP types for Lua
This library is published on luarocks
luarocks install luncheon
Luncheon provides Lua tables that represent HTTP Request
s and Response
s and a way to parse or
build them.
Both Request
and Response
expose a constructor source
, which expects the only argument to be a
function that will return a line when called (at least until the end of the headers
see the source docs for more details). So a simple
example of that might look like this.
local Request = 'luncheon.request'
local req_lines = {
'GET / HTTP/1.1',
'Content-Length: 0',
''
}
local req = Request.source(function()
return table.remove(req_lines, 1)
end)
assert(req.method == 'GET')
assert(req.url.path == '/')
assert(req.http_version == 1.1)
assert(req:get_headers():get_one('content_length') == 0)
local Response = 'luncheon.response'
local res_lines = {
'HTTP/1.1 200 Ok',
'Content-Length: 0',
''
}
local res = Response.source(function()
return table.remove(res_lines, 1)
end)
assert(res.status == 200)
assert(res.status_msg == 'Ok')
assert(res.http_version == 1.1)
assert(res:get_headers():get_one('content_length') == 0)
Notice how in both of the above examples, the lines do not contain a new line character, this is
because Lua's normal methods for reading IO, will omit a trailing new line. For example
io.open('README.md'):read('*l')
returns '# Luncheon'
with no trailing new line.
To handle some common use cases, the utils
module provides a source wrapper around luasocket's tcp
and udp sockets.
Both Request
and Response
expose a constructor new
along with the serialize
and iter
methods for building them and then converting them into "hypertext".
local Request = require 'luncheon.request'
local req = Request.new('GET', '/')
:add_header('Host', 'example.com')
:append_body('I am a request body')
for line in req:iter() do
print(string.gsub(line, '\r?\n$', ''))
end
local Response = require 'luncheon.response'
local res = Response.new(200)
:add_header('age', '2000')
:append_body('I am a response body')
print(res:serialize())
Notice how the req:iter()
loop has to remove new lines before printing. That means we already get
the CRLF
line endings required for the start line and headers.
This example uses luasocket to receive
incoming HTTP Request
s and echo them back out as Response
s.
Request = require 'luncheon.request'
Response = require 'luncheon.response'
utils = require 'luncheon.utils'
socket = require 'socket'
tcp = assert(socket.tcp())
assert(tcp:bind('0.0.0.0', 8080))
assert(tcp:listen())
while true do
local incoming = assert(tcp:accept())
local req = assert(Request.tcp_source(incoming))
print('Request')
print('url', req.url.path)
print('method', req.method)
print('body', req:get_body())
local res = Response.new(200, incoming)
:add_header('Server', 'Luncheon Echo Server')
:append_body(req:get_body())
:send()
incoming:close()
end
See the examples directory for more