The Auxiliary Lua API

The auxiliary Lua APIs provide additional functionality, including HTTP(S) client libraries, an email (SMTP) client, (secure) sockets, and SSL certificate management.

These APIs are optional and are not part of the standard Lua libraries. They must be compiled and integrated into your C/C++ build. The source code for these APIs can be found in the xrc directory. The Mako Server and Xedge example servers include all auxiliary APIs. Refer to one of those examples for how to include and initialize the optional auxiliary Lua APIs.

Universal Binary JSON

The UBJSON API is identical in functionality to the standard JSON API, but uses Universal Binary JSON as the encoding format and not the standard JSON format.

ba.ubjson.parser()
ba.ubjson.encode(table [,table] [,size])
ba.ubjson.decode(data [,stacksize [,namelen [,offset]]])
-- Prints ["Hello World"]
print(ba.json.encode(ba.ubjson.decode(ba.rndbs(5)..ba.ubjson.encode{"Hello World"},1,0,5)))

ByteArray

The ByteArray, which is included in the Socket library, is a mutable companion to Lua's immutable string type. The ByteArray automatically converts to a string, when needed. In addition, the socket:write method can, in most cases, use the ByteArray directly without doing any additional copying. The ByteArray supports the following meta-methods: __index, __newindex, __tostring, and __len.

ba.bytearray.create(size)
ba.bytearray.create(string)

Create a 'size' length ByteArray or create a ByteArray from string.

ba.bytearray.copy(to-array, n, string|array [, i [, j]])

Copy a string or bytearray that optionally starts at i and continues until j to to-array (argument 1) at position n. The arguments i and j can be negative. If i is absent, it is assumed to be equal to 1. If j is absent, it is assumed to be equal to -1 (which is the same as the array length). The source string or source bytearray can be larger than what can fit in to-array. Only the amount that fits in to-array is copied. The function returns the number of bytes that were not copied (the overflow amount).

Note that a string or bytearray can also be copied by using indices, e.g., mybytearray[5] = "hello". However, using indices throws an exception if the source data cannot fit in to-array.

ba.bytearray.h2n(array, n, size, number)

Host (integer) to network (big endian data stream) conversion.

n - Array offset position; 1 to length - size.
size - number of bytes to convert from the number -- e.g., 2 converts a 16-bit number and 4 converts a 32-bit number.
number - the number to convert.

The function stores the converted data in the bytearray at position 'n'.

ba.bytearray.n2h(array, n, size)

Network (big endian data stream) to host (integer) conversion.

n - Array offset position; 1 to length - size.
size - the byte size to read and convert -- e.g., 2 converts a 16-bit number and 4 converts a 32-bit number.

The function returns the converted data as a number in host endian format.

ba.bytearray.setsize(array, i [, j])

Changes the array's start and optionally, the end marker. Changing the start and end markers makes it possible to (temporarily) shrink the array to a smaller size. All meta-methods are affected by calling this function. The start and end markers i and j can be negative. If j is absent, then it is assumed to be equal to -1.

ba.bytearray.size(array)

Returns three values: the array's real size, the start marker position, and the end marker position. If ba.bytearray.setsize() has not been called, the start marker is one and the end marker is the allocated array size.

ba.bytearray.tostring(array, [, i [, j]])

Returns a (sub)string representation of the array that optionally starts at i and continues until j; i and j can be negative. If i is absent, then it is assumed to be equal to 1, If j is absent, then it is assumed to be equal to -1 (which is the same as the array length).

Examples

In the following examples, the __tostring metamethod is triggered when the array is printed.

local array = ba.bytearray.create(23)
for i=1,20 do array[i]=64+i end

print(#array) -- 23
ba.bytearray.setsize(array,1,20)
print(#array) -- 20
print(ba.bytearray.size(array)) -- 23	1	20
print(array) -- ABCDEFGHIJKLMNOPQRST

local t={}
 -- #array length is 20
for i=1,#array do table.insert(t,array[i]) end
ba.bytearray.setsize(array) -- same as: (array,1,-1); length is now 23

array[1]="abcdefghijklmnopqrstuvz"
print(array) -- abcdefghijklmnopqrstuvz

array[4]=t -- Copy table into ByteArray starting at position 4
print(array) -- abcABCDEFGHIJKLMNOPQRST

ba.bytearray.setsize(array,-3) -- Same as (array,-3,-1)
print(#array,array) -- 3	RST

ba.bytearray.setsize(array) -- Restore normal size
for i=1,#array do array[i]='*' end -- Fill with '*'

ba.bytearray.setsize(array,4)
array[1]=t -- Copy table
print(array) -- ABCDEFGHIJKLMNOPQRST
ba.bytearray.setsize(array,1,-4)
print(array) -- ***ABCDEFGHIJKLMNOPQ

Crypto Library

ba.crypto.hash([algorithm [, algorithm, key]])

Creates and returns a cryptographic hash function with the given algorithm, which can be used to generate hash digests. The algorithm defaults to sha1 if no arguments are provided. Valid options for the first argument "algorithm" are one of the following strings: md5, sha1, sha256, sha384, sha512, and hmac. The optional second and third argument must be provided when the first argument is hmac. The second and third arguments are ignored for all other hash functions. When the first argument is hmac, the second argument must be one of the following: "md5", "sha1", "sha256", "sha384", or "sha512". The third argument "key" is the secret HMAC key.

The returned function accepts the following arguments:

The following example shows how to calculate B64(MD5(username password)):

local hfunc = ba.crypto.hash"md5"
hfunc(username)
hfunc(password)
local data = hfunc(true,"b64")

The hash function returns a copy of itself when being fed data, thus making it possible to chain the functions. The following example produces the same result as the above example:

local data = ba.crypto.hash"md5"(username)(password)(true,"b64")

The following example shows how to create an MD5 HMAC for the username and password by using the secret key "qwerty".

local data = ba.crypto.hash("hmac","md5","qwerty")(username)(password)(true,"b64")
DK = ba.crypto.PBKDF2(PRF, Password, Salt, c, dkLen)

PBKDF2 is a simple cryptographic key derivation function, which is resistant to dictionary attacks and rainbow table attacks. It is based on iteratively deriving HMAC many times with some padding. The PBKDF2 algorithm is described in the Internet standard RFC 2898 (PKCS #5).

Asymmetric Encryption

This API provides encryption, decryption, signing, and verification using RSA and ECC with X.509 keys and certificates.

Functions:

ba.crypto.keysize(key [,op])
Returns two values: the key size of the private key and "RSA" or "ECC". The optional op argument may include the password option for password-protected keys. Throws an error on incorrect arguments.
ba.crypto.encrypt(plaintext, cert [,op])
Encrypts the plaintext using the public key. Returns the ciphertext.
ba.crypto.decrypt(ciphertext, key [,op])
Decrypts the ciphertext using the private key. Returns the plaintext on success or nil,error on failure.
ba.crypto.sign(hash, key [,op])
Signs the hash using the private key. Returns the digital signature. The hash type can be one of: "sha1", "sha256", "sha384", and "sha512". See also ba.tpm.sign().
ba.crypto.verify(signature [,cert], hash [,op])
Verifies whether the signature matches the provided hash using the public key. Returns true if valid, otherwise false.

Function Arguments:

Limitations:

Symmetric Encryption

ba.crypto.symmetric(algorithm, key, IV [, mode])

Encrypt or decrypt data using a symmetric cipher.

Arguments:

Returns an object with the following methods:

s:setauth(auth)
Set the optional Additional Authenticated Data (AAD) for GCM and CCM.
s:encrypt(data [,"PKCS7"])
Encrypt data. The function returns the encrypted data and a 16 byte tag (hash) when using the cipher GCM and CCM. Max data size is 0xFFF0. Larger data sets must be encrypted in chunks. The argument "PKCS7" must be set for the last encrypted chunk when using CBC or CCM.
s:decrypt(encdata [,tag [,"PKCS7"]])
Decrypt data. The tag (hash) is required when using the cipher GCM and CCM. Max data size is 0xFFF0.

Mode Behavior:

Padding:

AES-GCM does not use PKCS7 block padding. For CCM/CBC-style chunked encryption, PKCS7 is used on the final chunk to pad/unpad plaintext.

Examples:

AES- GCM, CCM, and CBC examples:

local key = "0123456789ABCDEF" -- Preferably use ba.rndbs(16) or ba.rndbs(32)
local iv = "0123456789AB" -- Preferably use ba.rndbs(12)
local message="Hello World!"

local gcmEnc = ba.crypto.symmetric("GCM", key, iv)
local gcmDec = ba.crypto.symmetric("GCM", key, iv)
local cipher,tag = gcmEnc:encrypt(message)
local data=gcmDec:decrypt(cipher,tag)
trace("GCM",#data,data)

local ccmEnc = ba.crypto.symmetric("CCM", key, iv)
local ccmDec = ba.crypto.symmetric("CCM", key, iv)
cipher,tag = ccmEnc:encrypt(message,"PKCS7")
data=ccmDec:decrypt(cipher,tag,"PKCS7")
trace("CCM",#data,data)

iv = "0123456789ABcdef" -- 16 bytes
local cbcEnc = ba.crypto.symmetric("CBC", key, iv, "encrypt")
local cbcDec = ba.crypto.symmetric("CBC", key, iv, "decrypt")
cipher = cbcEnc:encrypt(message,"PKCS7")
data=cbcDec:decrypt(cipher,"PKCS7")
trace("CBC",#data,data)

Fernet (AES-CBC) Decrypt Example:

Fernet is common in the Python world and provides a simple method for encrypting and decrypting a message. The following article shows how to use it from Python as well as providing a good introduction to the format: pythoninformer.com.

local function decodeFernet(key,token)
   key,token=ba.b64decode(key),ba.b64decode(token)
   if key and token and token:byte(1) == 0x80 then
      local signingKey,encryptionKey=key:sub(1,16),key:sub(17)
      local iv,cipher,hmac = token:sub(10,25),token:sub(26,-33),token:sub(-32)
      if hmac ~= ba.crypto.hash("hmac","sha256",signingKey)(token:sub(1,-33))() then
         return nil, "Invalid HMAC"
      end
      local datetime = ba.datetime(ba.socket.n2h(8,token,2))
      local plain=ba.crypto.symmetric(
         "CBC",encryptionKey,iv,"decrypt"):decrypt(cipher,"PKCS7")
      return plain,datetime
   end
   return nil, "Invalid key or token"
end

local key="M516vNeYFaEauWp_C7Dovyms7ZF1xNyizKPFZ4ucBS0="
local token=[[
gAAAAABftfpRGg8HwvomaO8Uj71gJgplLDJH05-
OcprsFgw2aAYt1b7ngdUv7vsfPGNdxr-WjpaFY8
gx9Gf-j8qiJrpFaVg3BQ==
]]

local text,datetime = decodeFernet(key,token)
if text then
   print("Text:",text)
   print("DateTime:",datetime)
else
   print("Error:",datetime) -- datetime is now error
end
  

Cryptographic Parameters

The following functions enable extraction of cryptographic parameters used with JSON Web Tokens and JSON Web Signatures.

ba.crypto.keyparams(key [, password])
Extracts and returns the public key parameters from an X.509 PEM-encoded private or public key, such as the key generated by ba.create.key().
ba.crypto.sigparams(signature)
Extracts and returns the signature parameters from a DER-encoded binary string, such as the return value from ba.crypto.sign. See the jwt.lua module for how to use this function.

JSON Web Token (JWT) Library

This API provides functions for signing and verifying JSON Web Tokens (JWTs) using HMAC (HSxxx), RSA (RSxxx), and ECDSA (ESxxx) algorithms.

-- Load the JWT module
local jwt = require"jwt"
jwt.sign(payload, secret [, options])

Generates a signed JWT using the specified algorithm. See also ba.tpm.jwtsign().

Parameters

Returns

jwt.verify(jwt, secret [,kid])

Decodes and validates a JWT.

Parameters

Returns

EventEmitter Library

This library provides event-driven functionalities for Lua.

Example:

local EventEmitter = require"EventEmitter"

-- Create an instance
local emitter = EventEmitter.create()

-- Register a listener
emitter:on("myEvent", function(msg) print("Received:", msg) end)

-- Emit an event
emitter:emit("myEvent", "Hello World")

-- Emit and retain an event
emitter:emit({name = "myEvent", retain = true}, "Retained Message")

-- Register a new listener, which immediately receives the retained message
emitter:on("myEvent", function(msg) print("New listener received:", msg) end)

EventEmitter API

Constructor

Methods

  1. E:on(event, cb)

  2. E:emit(event, ...)

  3. E:removeListener(event, cb2rem)

forkpty

The forkpty library provides a combined fork, exec, and child process pseudo-terminal. The code is available for Linux, Mac, and QNX. Windows users may use the simpler io.popen library. For simpler tasks, use ba.exec.

The code can be used for executing and managing Linux executables such as "ls", "kill", etc. The forkpty library provides advanced child process management.

Blocking Read Example

local pty,err = ba.forkpty("/bin/sh")
pty:write"ls -l\n"
local data,err = pty:read(500)
while(data) do
   print("data:",data)
   data,err = pty:read(500)
end
pty:terminate()

Download: the Web Shell, a fully working web-based terminal (alternative to using SSH), which is using the forkpty library for the shell process management and communication. The CGI plugin is also using the forkpty library for process management.

pty,err=ba.forkpty([X,] prog [, args...])

Arguments:

Starts program prog in a separate process and returns a process object that is used for child process management. The pty process object support full duplex read and write operations.

On success, the function returns a pty child process communication object with 4 methods:

  1. pty:read: Read child's stdout.
  2. pty:write: Write to child's stdin.
  3. pty:pause: Pause or resume child process.
  4. pty:winsize: Set the child's terminal screen size.
  5. pty:close: Gracefull kill.
  6. pty:terminate: Immediate kill.

The external process can be managed in two modes, polling, or asynchronous read. Polling the pty is typically used when designing web-clients that use AJAX for polling the child process for new data. Asynchronous read is typically used together with SMQ, where a web-client is using a persistent asynchronous communication channel with the server. The optional "function" must be supplied for asynchronous read operation. The default mode is designed for polling mode. The examples below assume that you do not provide a function. Asynchronous mode is explained later.

PTY Methods

pty:read([bool|number])

Read stdout,stderr from the child process.

Note: when operating in asynchronous mode, pty:read() always blocks and the arguments are ignored.

Return values:

Example:
local data,err = pty:read()
if data then
 -- manage data
elseif err then
  if err = "terminated" then
    -- Terminated
  else
    -- pty error
  end
end
ok,err = pty:write(string)

Send data to the child process's stdin.

ok is nil on error. See pty:read for how to manage the error code.

pty:pause(true|false)

Pause or resume a paused child process.

ok,err=pty:winsize(lines, cols)

Set the child's terminal screen size.

local status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:close([true])

Return the exit code for the <defunc> child process or gracefully kill the active child process.

The function returns immediately unless the optional parameter is set to true. The function waits for the child to terminate if the optional parameter is set to true.

status is the child process exit code.

Status codes:

If status is nil, the second parameter is the error message. The child process is immediately killed if any errors are detected.

Example:
local status, WIFEXITED, WIFSIGNALED, WTERMSIG = pty:close()
if status == nil then
   print("Failed, msg:", WIFEXITED)
elseif status < 0 then
   print("Running")
elseif status == 0 then
   print("Exited");
else
   print("Exited with error code:", status , WIFEXITED, WIFSIGNALED, WTERMSIG);
end

Note: pty:close() is designed to be called repeatedly until the child terminates, i.e., until it returns a value that is not less than zero.

local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()

Close the connection to the <defunc> child process or immediately kill the active child process.

Asynchronous PTY Mode

The PTY object is self-referencing in asynchonous mode. Normally, a reference must be kept to the pty objected returned by ba.forkpty to prevent garbage collection (GC). When in asynchronous mode, a reference is automatically created by ba.forkpty to prevent GC. This reference is valid as long as the coroutine executes.

The following example shows how to use asynchronous mode:

-- The asynchronous receive function keeps reading until the process terminates
function recData(pty)
   while true do
      local d,e = pty:read() -- Read data. Arguments to pty:read are ignored.
      if not d then -- Child process terminated
         break
      end
      trace(d,e)
   end
   trace("\nexit coroutine", e and e or "OK")
   -- coroutine automatically cleans up on exit, but we are allowed to call:
   -- local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()
end

local pty,err -- Local variables i.e. we do not need a reference to prevent GC
pty,err = ba.forkpty(recData, "/bin/ls", "-l") -- Execute ls -l
trace(pty,err)

In asynchronous mode, a function must be provided as the first argument to ba.forkpty. This function is executed as a Lua coroutine and is automatically yielded by read when no data is available. The function is resumed when there is data available or if the child process terminates.

All pty:xxx functions operate as normal, except for read when called from within the coroutine.

HTTP(S) Client Libraries

The Barracuda HTTP(S) client library, which is implemented in C code, can be accessed from Lua by using the Lua bindings for the HTTP implementation. The Lua bindings are found in xrc/lua/lhttp.c.

In addition to the low level C implementation, two additional HTTP libraries implemented in Lua are provided. The additional libraries wrap around the low level library and simplify the use of the low level implementation.

The libraries are loaded as follows:

The HTTP(S) Client Libraries implementation conforms to the HTTP/1.1 standard, RFC 2616.

The HTTP client libraries use blocking socket calls and should therefore run in the context of the Server's Thread Pool such as an LSP page or the Lua Thread Library. See Thread Mapping for more information.

local http = require"httpc".create()
ba.thread.run(function()
    http:request{url="https://x.com"}
    trace(http:read"*a")
end)

The following example shows how to send JSON data as part of the HTTP body to a JSON echo service. The service responds with the original data and additional JSON data. Note that many JSON services are designed to accept POST data as URL encoded data and not directly as JSON as used in the following example. The method http:json() has been specifically designed to post JSON using URL encoded key/value pairs.

local rdata -- rec data
local sdata={ -- data to send
   hello = "world",
   vector = { i=10, j=15 }
}

local http = require"httpc".create()

local ok,err=http:request{
   url="https://postman-echo.com/post",
   method="POST",
   header={["Content-Type"]="application/json; charset=utf-8"}
}
if ok then
   ok,err=http:write(ba.json.encode(sdata))
   if ok then
      rdata,err=http:read"*a"
   end
end
if rdata then
   print("Rec Data:", rdata)
   print("Valid JSON: ", ba.json.decode(rdata) and "yes" or "no")
else
   print("Err:",err)
end

The "httpc" library

The library is loaded and an HTTP instance is created as follows:

local http = require"httpc".create([op])
The HTTP Option Table:

Returns an instance of the low level HTTP library.

The option table:
  • shark = SharkSSL object -- The SharkSSL object is required when using secure connections (when the URLs starts with https://). The SharkSSL object provided by ba.sharkclient() is used if the shark attribute is not set for HTTPS connections.
  • persistent=bool -- Set/disable persistent HTTP1.1 connections. The Default is to enable persistent connections.
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Use of IPv6 address translation. Note, the server must have been compiled with IPv6 support.
  • proxy = string -- Use a HTTPS or SOCKS5 proxy with IP address or domain name "proxy".
  • proxyport = number -- Proxy port number defaults to 8080 for HTTPS proxy and 1080 for SOCKS5 proxy.
  • socks = bool -- Set to true if you are using a SOCKS5 proxy. The default is to use HTTPS proxy by using HTTP CONNECT.
  • proxyuser = string -- Proxy authenticate using username.
  • proxypass = string -- Proxy authenticate using password.
  • proxycon = bool -- Set to true to initiate proxy connection but without sending any data to the server. The HTTP client library may be used to initiate socket connections via a proxy for other protocols. When the required proxy parameters are set and when proxycon is set to 'true', a connection is established via the requested proxy when http:request() is called. If successful, http:request() returns the special error code "prxready". See the MQTT proxy example for details.

Methods:

The methods associated with the object returned by require"httpc".create()

http:timeout(milliseconds)

Set the number of milliseconds before connect, read, and write time out. The default is 20000, i.e., 20 seconds. A value of zero waits indefinitely. The maximum value is 3,240,000.

http:request(op)

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some form of socket error occurred. The number is only returned if a proxy is installed. The number corresponds to the HTTP status code, for example 407 Proxy Authentication Required.

The option table:
Required attributes:
  • url = string -- URL to the server resource.
  • method = string -- Valid values: DELETE, GET, HEAD, PATCH, POST, and PUT.
Optional attributes:
  • header = table -- A table with key value pairs. Include additional HTTP headers.
  • query = table -- A table with key value pairs. Include url encoded data in the query component of the request URL. Example: query={key1="a", key2={"b1", "b2", "b3"}}
  • user = string -- Authenticate using username.
  • password = string -- Authenticate using password.
  • size = number -- Size is an optional length when sending data to a server using POST or PUT. The client sends data using chunked transfer encoding if no size is specified.
  • trusted = bool -- Force method http:request to accept only trusted connections when connecting to a server. This option requires that you have a SharkSSL object with an installed Certificate Authority Store. The connection is closed and the function returns nil,"nottrusted" if the certificate is not trusted, expired, or if the domain name does not match the domain name in the certificate. See also http:trusted.
http:status()

Returns the HTTP server response code.

http:header()

Returns a table with the HTTP header key/value pairs.

http:headerpairs()

Returns an iterator that will traverse all the HTTP header key/value pairs.

http:cookie()

Returns an iterator that will extract and traverse all cookies sent by the server.

The server may send multiple "Set-Cookie" headers:

   Set-Cookie: name=value [; expires=date] [; path=path] [; domain=domain] [; secure]

The iterator returns the cookie name, value, and an optional table. The table is returned if the cookie returned by the server contains more than just the cookie name. The table provides {key, value} pairs, where the keys can be one of the following: expires, path, domain, and secure.

local http = require"httpc".create()
local ok,err=http:request{url="https://www.google.com/"}
if ok then
   for name,value,t in http:cookie() do
      print("cookie name:", name, ",value", value)
      if t then
         for k,v in pairs(t) do
            print(k,v)
         end
      end
   end
end
http:read(size)

Read size bytes.

size is one of:
  • "a" for all data.
  • Number(n) for reading n bytes.
  • Not provided. Makes the read function silently consume and discard response data.
http:write(n)

Write n bytes.

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some kind of socket error occurred. The number is the HTTP response code. For example, if uploading data fails and the server sends an HTTP response code while the client is uploading, the upload is aborted immediately and the HTTP response code is returned to the caller.

The caller can set a "Expect: 100-continue" header prior to uploading data, but this is not necessary since the write function asynchronously detects if the server denied the upload. Any 100 continue messages sent by the server are silently consumed by the write function.

Note: The data is uploaded using chunked transfer encoding unless the size attribute was set on the http:request() option table. Not all servers support chunked transfer encoding.

http:certificate()

Returns the peer's certificate chain as a table if the connection is secure and the peer sent a certificate. A second return value is set to true if the certificate is valid, i.e., if it was validated by the certificate store.

http:cipher()

Returns the cipher suite and SSL protocol version being used if the connection is secure. See socket:cipher() for more information on the return values.

http:trusted()

Returns the peer's "trust" status.

http:peername()

Returns a string of the client name/IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.

http:sockname()

Returns a string of the server name/IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.

http:close()

Close the connection.

The "http" library

The "http" library extends the httpc library and simplifies the use of the low level implementation when HTTP redirect management is required.

The library is loaded and an HTTP instance is created as follows:

local http = require"http".create([op])

Redirect management:

Many server applications may send a 302 temporary redirect request or a 301 permanent redirect request when accessing resources. The server resource may for example redirect to the same resource, but request the client to add or change URL encoded data when accessing the server. The "http" library automatically manages 301 and 302 requests. The client library computes the new URL and sends a new request. This sequence continues until the server returns a non 301/302 response or the redirect counter detects an infinite redirect loop. The extra http:url() method provided by the "http" library returns the computed URL and query data the server may have requested the client to perform.

Default http:request() options:

One of the benefits of the Lua implemented wrapper is that it simplifies the use of the option table. Options that are required on method http:request() can be set when creating the HTTP client instance. The following example illustrates this:

local http=require"http"
 
-- Set default options
local op={
   url="https://realtimelogic.com/products/",
   method="GET" -- Not needed since it is the default
}
local h=http.create(op)
 
 -- Use default options: method "GET" and url https://realtimelogic.com/products/"
h:request()
-- Prints nil since response is generated by LSP and is chunk encoded
print("GET: Content-Length",h:header()["Content-Length"])
print("Calculated length", #h:read"a")
 
h:request{method="HEAD"} -- Override default "GET" method
-- This works.
print("HEAD: Content-Length",h:header()["Content-Length"])
 
h:request() -- We are using "GET".
-- prints nil
print("GET: Content-Length",h:header()["Content-Length"])
h:close() -- We are done

An instance of the "httpc" library would have thrown an exception when calling h:request(), but the Lua version remembers the extra options added when we created the object.

You can optionally provide an option table and override any of the default settings. The HEAD request above illustrates this.

Methods:

The "http" library provides, in addition to the httpc methods, the following method:

http:url()

Returns the url and query data as a table for the current connection. The url and query data may be different on server response than the values set when connecting since the server can send redirect responses. The redirect response is internally managed by the "http" library.

The "httpm" library

The "httpm" (HTTP Managed) library extends the http library and simplifies common tasks such as uploading and downloading data, sending HTTP POST requests, and communicating with a server using JSON.

The library is loaded and an HTTP instance is created as follows:

local http = require"httpm".create([op])

Methods:

The "httpm" library provides, in addition to the httpc and http methods, the following methods:

http:stat(url [,op])

Returns a table of attributes for the named url -- the resource . If the resource does not exist, 'nil' followed by an error code is returned. The fields of the table are named as follows:

This function is by default using the HTTP method "HEAD". You can change the HTTP method by setting op={method="method-type"}

Examples:

local http = require"httpm"
local h=http.create()
-- Dynamic resource
local st,err=h:stat("https://realtimelogic.com/")
-- prints val,nil
if st then print("A:",st.size,st.mtime) end
-- Static resource
st,err=h:stat("https://realtimelogic.com/downloads/docs/IoT-Security-Solutions.pdf")
-- prints val,val
if st then print("B:",st.size,st.mtime) end
-- Dynamic resource using HTTP GET
st,err=h:stat("https://realtimelogic.com/",
              { method="GET", query={foo="bar"}})
-- prints nil,nil
if st then print("C:",st.size,st.mtime) end
h:close()
-- Static resource using HTTP GET
st,err=h:stat("https://realtimelogic.com/downloads/docs/IoT-Security-Solutions.pdf",
              { method="GET"})
-- prints val,val
if st then print("D:",st.size,st.mtime) end
http:post(url,tab [,op])

Send an HTTP POST request to a server. This function emulates an HTML form submission in a browser.

The method returns http-status,response-body on success and nil,error-msg on error. Note: the method fails if posting to a server that sends either 301 or 302 redirect. The post method returns 'nil, status' and http:status() returns 'nil, status, url', where status is 301 or 302 and url is the redirect URL.

The following example is sending url encoded data in the query component of the URL and in the body of the message.

local http = require"httpm".create()
local ok,data=http:post("https://postman-echo.com/post",
                    {formKey1="formVal1",formKey2="formVal2"},
                    {query={queryK1="queryVal1",queryK2="queryVal2"}})
print(ok) -- HTTP 200
print("JSON resp:",data)
 -- Postman Echo returns everything packaged as json
data=ba.json.decode(data)
print"\nArgs (query):"
for k,v in pairs(data.args) do print("",k,v) end
print"\nForm (POST body):"
for k,v in pairs(data.form) do print("",k,v) end
http:upload(conf [,op])

Upload a file to a server. This function is by default using the HTTP method "PUT". You can change the HTTP method by setting op={method="POST"}

The method returns ture on success and nil,error-msg on error. Note: the upload method fails if uploading to a server that sends either 301 or 302 redirect. The upload method returns true for success, but http:status() returns 'nil, status, url', where status is 301 or 302 and url is the redirect URL.

The function can be used in two modes:


The HTTP library is monitoring response data from the server while the upload is in progress. The upload is automatically terminated if the server sends an error response. It is for this reason not necessary to do the typical one byte test upload prior to uploading the actual data to test if the server accepts the upload. HTTP 100-continue messages sent from the server are silently consumed.

http:download(conf [,op])

Download a file from a server and save the file. The function can be used in two modes:

http:json(url, data [,op])

Load JSON-encoded data from the server using an HTTP GET request. This function, which is designed for server-side Lua code, is similar in functionality to the jQuery.getJSON() function in the jQuery JavaScript library.

Example 1:

local http=require"httpm".create()
local data={} -- Empty, no key/value pairs
local t,err=http:json("http://ip.jsontest.com/",data)
if t then -- If we got a Lua table (decoded JSON string)
   print("IP address:",t.ip)
end

Example 2:

local http=require"httpm".create()
local data={lat=33.466972,lng=-117.698105} -- Dana Point, CA
local t,err=http:json("http://api.sunrise-sunset.org/json",data)
if t then
   print(require"serpent".block(t,{comment=false}))
end

Example 3:

local http=require"httpm".create()
-- Connect to a Web-File-Manager in a Barracuda server
-- and request a JSON directory listing.
local data={cmd="lj"}
local t,err = http:json("https://tutorial.realtimelogic.com/fs/home/mako", data)
if t then
   for _,r in ipairs(t) do -- Iterate all resources
      response:write("name=",r.n,", size=",r.s < 0 and "DIR" or r.s,
                     ", date=",os.date("%c",r.t),"<br>")
   end
else
   response:write(err)
end

See also how to send JSON as part of the HTTP body.

The Lua Debug Module

The Lua debug module dbgmon implements the Debug Adapter Protocol and can be used by any debugger implementing this protocol. Module dbgmon is implemented in C code and interfaces to the Lua debug API.

See the how to use the debugger example on GitHub for more information on how to use this module.

Load the module as follows:

local dbgmon = require"dbgmon"

The returned value dbgmon is a table with the following functions:

dbgmon.connect(op)

The function establishes a persistent TCP connection with a debugger. The function can be configured to operate as a TCP client or TCP server. The function operates as a TCP client and attempts to connect to "localhost:4711" if called without arguments. The function does not return until a debugger TCP connection is established or a TCP error occurs. The argument 'op' is an optional configuration table that takes the following values:

dbgmon.close()

Close any active TCP debugger connection and resume normal operation.

dbgmon.pause()

Pause the debug session and resume normal operation without closing the TCP debugger connection.

dbgmon.resume()

Resume a paused debug session.

LuaIo

The LuaIo module implements Lua mapping between the Barracuda I/O interface and Lua. The LuaIo API makes it possible to implement a Barracuda I/O interface using Lua.

The purpose with the LuaIo is to provide filtering on existing I/O's or implement new I/O types in Lua. As an example, a SQL mapped LuaIo could provide WebDAV access to a SQL database.

Ready-to-run Examples: