Moai Cloud HTTP Boilerplate
- Details
- Category: Blog
- Published on Monday, 23 July 2012 23:25
- Written by Patrick Meehan
For HTTP requests Moai Cloud exposes curl and not much else. I've posted some HTTP boilerplate below the fold. This was hastily adapted from my own service production code. I'm posting it now for use in my next blog post. I'll be formally releasing a production-hardened version of this code (along with my web service utilities) in the near future so stay tuned.
local _private = {}
http = http or {}
ANY = 'ANY'
DELETE = 'DELETE'
GET = 'GET'
POST = 'POST'
PUT = 'PUT'
--============================================================--
-- _private
--============================================================--
----------------------------------------------------------------
function _private.http ( verb, url, query, body, timeout )
local url = url .. http.buildQueryString ( query )
local c = assert ( curl.new (), "Could not initialize Curl." )
local file = ''
c:setopt ( curl.OPT_WRITEFUNCTION, function ( stream, buffer )
file = file .. buffer
return string.len ( buffer )
end );
c:setopt ( curl.OPT_CUSTOMREQUEST, verb )
c:setopt ( curl.OPT_BUFFERSIZE, 2^13 )
c:setopt ( curl.OPT_HTTPHEADER, 'Connection: Keep-Alive', 'Accept-Language: en-us' )
c:setopt ( curl.OPT_URL, url )
c:setopt ( curl.OPT_CONNECTTIMEOUT, timeout or 15 )
if body then
local ok, jsonString = pcall ( json.encode, body )
if ok then
c:setopt ( curl.OPT_POSTFIELDS, jsonString )
c:setopt ( curl.OPT_POSTFIELDSIZE, #jsonString )
end
end
local ok, err = c:perform ()
if ok then return file end
return nil, err
end
--============================================================--
-- http
--============================================================--
local DEFAULT_TIMEOUT = 30
----------------------------------------------------------------
function http.buildQueryString ( t )
if not t then return '' end
local sorted = {}
for k, v in pairs ( t ) do
local encoded = urlEncode ( k ) .. '=' .. urlEncode ( v )
table.insert ( sorted, encoded )
end
table.sort ( sorted )
local str = table.concat ( sorted, '&' )
if #str > 0 then
str = '?' .. str
end
return str
end
----------------------------------------------------------------
function http.delete ( url, query, timeout )
return _private.http ( DELETE, url, query, nil, timeout )
end
----------------------------------------------------------------
function http.get ( url, query, timeout )
return _private.http ( GET, url, query, nil, timeout )
end
----------------------------------------------------------------
function http.post ( url, query, body, timeout )
return _private.http ( POST, url, query, body, timeout )
end
----------------------------------------------------------------
function http.put ( url, query, body, timeout )
return _private.http ( PUT, url, query, body, timeout )
end
----------------------------------------------------------------
function http.urlDecode ( data )
return data:gsub ( '%+', ' ' ):gsub ( '%%(%x%x)', function ( s )
return string.char ( tonumber ( s, 16 ))
end )
end
----------------------------------------------------------------
function http.urlEncode ( str )
if str then
-- convert line endings
str = string.gsub ( str, "\n", "\r\n" )
-- escape all characters but alphanumeric, '.' and '-'
str = string.gsub ( str, "([^%w%.%- ])",
function ( c )
return "%" .. string.format ( "%02X", string.byte ( c ))
end
)
-- convert spaces to "+" symbols
str = string.gsub ( str, " ", "+" )
end
return str
end
----------------------------------------------------------------
function http.urlParse ( data, sep )
local result = {}
sep = sep or '&'
data = data .. sep
for piece in data:gmatch("(.-)" .. sep) do
local k,v = piece:match("%s*(.-)%s*=(.*)")
if k then
result[http.urlDecode(k)] = http.urlDecode(v)
else
result[#result + 1] = http.urlDecode(piece)
end
end
return result
end

2013