MEDIUM: lua: Add ability for actions to intercept HTTP messages

It is now possible to intercept HTTP messages from a lua action and reply to
clients. To do so, a reply object must be provided to the function
txn:done(). It may contain a status code with a reason, a header list and a
body. By default, if an empty reply object is used, an empty 200 response is
returned. If no reply is passed when txn:done() is called, the previous
behaviour is respected, the transaction is terminated and nothing is returned to
the client. The same is done for TCP streams. When txn:done() is called, the
action is terminated with the code ACT_RET_DONE on success and ACT_RET_ERR on
error, interrupting the message analysis.

The reply object may be created for the lua, by hand. Or txn:reply() may be
called. If so, this object provides some methods to fill it:

  * Reply:set_status(<status> [  <reason>]) : Set the status and optionally the
   reason. If no reason is provided, the default one corresponding to the status
   code is used.

  * Reply:add_header(<name>, <value>) : Add a header. For a given name, the
    values are stored in an ordered list.

  * Reply:del_header(<name>) : Removes all occurrences of a header name.

  * Reply:set_body(<body>) : Set the reply body.

Here are some examples, all doing the same:

    -- ex. 1
    txn:done{
        status  = 400,
        reason  = "Bad request",
        headers = {
            ["content-type"]  = { "text/html" },
            ["cache-control"] = { "no-cache", "no-store" },
        },
        body = "<html><body><h1>invalid request<h1></body></html>"
    }

    -- ex. 2
    local reply = txn:reply{
        status  = 400,
        reason  = "Bad request",
        headers = {
            ["content-type"]  = { "text/html" },
            ["cache-control"] = { "no-cache", "no-store" }
        },
        body = "<html><body><h1>invalid request<h1></body></html>"
    }
    txn:done(reply)

    -- ex. 3
    local reply = txn:reply()
    reply:set_status(400, "Bad request")
    reply:add_header("content-length", "text/html")
    reply:add_header("cache-control", "no-cache")
    reply:add_header("cache-control", "no-store")
    reply:set_body("<html><body><h1>invalid request<h1></body></html>")
    txn:done(reply)
This commit is contained in:
Christopher Faulet 2020-01-31 12:21:52 +01:00
parent 2c2c2e381b
commit 700d9e88ad
2 changed files with 649 additions and 34 deletions

View File

@ -1733,18 +1733,86 @@ TXN class
:param class_txn txn: The class txn object containing the data.
:param string var: The variable name according with the HAProxy variable syntax.
.. js:function:: TXN.done(txn)
.. js:function:: TXN.reply([reply])
Return a new reply object
:param table reply: A table containing info to initialize the reply fields.
:returns: A :ref:`reply_class` object.
The table used to initialized the reply object may contain following entries :
* status : The reply status code. the code 200 is used by default.
* reason : The reply reason. The reason corresponding to the status code is
used by default.
* headers : An list of headers, indexed by header name. Empty by default. For
a given name, multiple values are possible, stored in an ordered list.
* body : The reply body, empty by default.
.. code-block:: lua
local reply = txn:reply{
status = 400,
reason = "Bad request",
headers = {
["content-type"] = { "text/html" },
["cache-control"] = {"no-cache", "no-store" }
},
body = "<html><body><h1>invalid request<h1></body></html>"
}
..
:see: :js:class:`Reply`
.. js:function:: TXN.done(txn[, reply])
This function terminates processing of the transaction and the associated
session. It can be used when a critical error is detected or to terminate
session and optionally reply to the client for HTTP sessions.
:param class_txn txn: The class txn object containing the data.
:param class_reply reply: The class reply object to return to the client.
This functions can be used when a critical error is detected or to terminate
processing after some data have been returned to the client (eg: a redirect).
To do so, a reply may be provided. This object is optional and may contain a
status code, a reason, a header list and a body. All these fields are
optionnals. When not provided, the default values are used. By default, with
an empty reply object, an empty HTTP 200 response is returned to the
client. If no reply object is provided, the transaction is terminated without
any reply.
The reply object may be fully created in lua or the class Reply may be used to
create it.
.. code-block:: lua
local reply = txn:reply()
reply:set_status(400, "Bad request")
reply:add_header("content-type", "text/html")
reply:add_header("cache-control", "no-cache")
reply:add_header("cache-control", "no-store")
reply:set_body("<html><body><h1>invalid request<h1></body></html>")
txn:done(reply)
..
.. code-block:: lua
txn:done{
status = 400,
reason = "Bad request",
headers = {
["content-type"] = { "text/html" },
["cache-control"] = { "no-cache", "no-store" },
},
body = "<html><body><h1>invalid request<h1></body></html>"
}
..
*Warning*: It not make sense to call this function from sample-fetches. In
this case the behaviour of this one is the same than core.done(): it quit
the Lua execution. The transaction is really aborted only from an action
registered function.
:param class_txn txn: The class txn object containing the data.
:see: :js:func:`TXN.reply`, :js:class:`Reply`
.. js:function:: TXN.set_loglevel(txn, loglevel)
@ -1791,6 +1859,98 @@ TXN class
See the HAProxy configuration.txt file keyword "http-request" action
"set-priority-offset" for details.
.. _reply_class:
Reply class
============
.. js:class:: Reply
**context**: action
This class represents an HTTP response message. It provides some methods to
enrich it.
.. code-block:: lua
local reply = txn:reply({status = 400}) -- default HTTP 400 reason-phase used
reply:add_header("content-type", "text/html")
reply:add_header("cache-control", "no-cache")
reply:add_header("cache-control", "no-store")
reply:set_body("<html><body><h1>invalid request<h1></body></html>")
..
:see: :js:func:`TXN.reply`
.. js:attribute:: Reply.status
The reply status code. By default, the status code is set to 200.
:returns: integer
.. js:attribute:: Reply.reason
The reason string describing the status code.
:returns: string
.. js:attribute:: Reply.headers
A table indexing all reply headers by name. To each name is associated an
ordered list of values.
:returns: Lua table
.. code-block:: lua
{
["content-type"] = { "text/html" },
["cache-control"] = {"no-cache", "no-store" },
x_header_name = { "value1", "value2", ... }
...
}
..
.. js:attribute:: Reply.body
The reply payload.
:returns: string
.. js:function:: Reply.set_status(REPLY, status[, reason])
Set the reply status code and optionally the reason-phrase. If the reason is
not provided, the default reason corresponding to the status code is used.
:param class_reply reply: The related Reply object.
:param integer status: The reply status code.
:param string reason: The reply status reason (optional).
.. js:function:: Reply.add_header(REPLY, name, value)
Add a header to the reply object. If the header does not already exist, a new
entry is created with its name as index and a one-element list containing its
value as value. Otherwise, the header value is appended to the ordered list of
values associated to the header name.
:param class_reply reply: The related Reply object.
:param string name: The header field name.
:param string value: The header field value.
.. js:function:: Reply.del_header(REPLY, name)
Remove all occurrences of a header name from the reply object.
:param class_reply reply: The related Reply object.
:param string name: The header field name.
.. js:function:: Reply.set_body(REPLY, body)
Set the reply payload.
:param class_reply reply: The related Reply object.
:param string body: The reply payload.
.. _socket_class:
Socket class
@ -2694,4 +2854,3 @@ OpenSSL:
* `https://github.com/brunoos/luasec/wiki
<https://github.com/brunoos/luasec/wiki>`_

View File

@ -190,6 +190,7 @@ static int class_http_ref;
static int class_map_ref;
static int class_applet_tcp_ref;
static int class_applet_http_ref;
static int class_txn_reply_ref;
/* Global Lua execution timeout. By default Lua, execution linked
* with stream (actions, sample-fetches and converters) have a
@ -4489,7 +4490,7 @@ __LJMP static int hlua_applet_http_send_response(lua_State *L)
/* Simple Protocol checks. */
if (isteqi(ist2(name, nlen), ist("transfer-encoding")))
h1_parse_xfer_enc_header(&h1m, ist2(name, nlen));
h1_parse_xfer_enc_header(&h1m, ist2(value, vlen));
else if (isteqi(ist2(name, nlen), ist("content-length"))) {
struct ist v = ist2(value, vlen);
int ret;
@ -5359,57 +5360,495 @@ __LJMP static int hlua_txn_set_priority_offset(lua_State *L)
return 0;
}
/* This function is an Lua binding that send pending data
* to the client, and close the stream interface.
/* Forward the Reply object to the client. This function converts the reply in
* HTX an push it to into the response channel. It is response to foward the
* message and terminate the transaction. It returns 1 on success and 0 on
* error. The Reply must be on top of the stack.
*/
__LJMP static int hlua_txn_forward_reply(lua_State *L, struct stream *s)
{
struct htx *htx;
struct htx_sl *sl;
struct h1m h1m;
const char *status, *reason, *body;
size_t status_len, reason_len, body_len;
int ret, code, flags;
code = 200;
status = "200";
status_len = 3;
ret = lua_getfield(L, -1, "status");
if (ret == LUA_TNUMBER) {
code = lua_tointeger(L, -1);
status = lua_tolstring(L, -1, &status_len);
}
lua_pop(L, 1);
reason = http_get_reason(code);
reason_len = strlen(reason);
ret = lua_getfield(L, -1, "reason");
if (ret == LUA_TSTRING)
reason = lua_tolstring(L, -1, &reason_len);
lua_pop(L, 1);
body = NULL;
body_len = 0;
ret = lua_getfield(L, -1, "body");
if (ret == LUA_TSTRING)
body = lua_tolstring(L, -1, &body_len);
lua_pop(L, 1);
/* Prepare the response before inserting the headers */
h1m_init_res(&h1m);
htx = htx_from_buf(&s->res.buf);
channel_htx_truncate(&s->res, htx);
if (s->txn->req.flags & HTTP_MSGF_VER_11) {
flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11);
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"),
ist2(status, status_len), ist2(reason, reason_len));
}
else {
flags = HTX_SL_F_IS_RESP;
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.0"),
ist2(status, status_len), ist2(reason, reason_len));
}
if (!sl)
goto fail;
sl->info.res.status = code;
/* Push in the stack the "headers" entry. */
ret = lua_getfield(L, -1, "headers");
if (ret != LUA_TTABLE)
goto skip_headers;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
struct ist name, value;
const char *n, *v;
size_t nlen, vlen;
if (!lua_isstring(L, -2) || !lua_istable(L, -1)) {
/* Skip element if the key is not a string or if the value is not a table */
goto next_hdr;
}
n = lua_tolstring(L, -2, &nlen);
name = ist2(n, nlen);
if (isteqi(name, ist("content-length"))) {
/* Always skip content-length header. It will be added
* later with the correct len
*/
goto next_hdr;
}
/* Loop on header's values */
lua_pushnil(L);
while (lua_next(L, -2)) {
if (!lua_isstring(L, -1)) {
/* Skip the value if it is not a string */
goto next_value;
}
v = lua_tolstring(L, -1, &vlen);
value = ist2(v, vlen);
if (isteqi(name, ist("transfer-encoding")))
h1_parse_xfer_enc_header(&h1m, value);
if (!htx_add_header(htx, ist2(n, nlen), ist2(v, vlen)))
goto fail;
next_value:
lua_pop(L, 1);
}
next_hdr:
lua_pop(L, 1);
}
skip_headers:
lua_pop(L, 1);
/* Update h1m flags: CLEN is set if CHNK is not present */
if (!(h1m.flags & H1_MF_CHNK)) {
const char *clen = ultoa(body_len);
h1m.flags |= H1_MF_CLEN;
if (!htx_add_header(htx, ist("content-length"), ist(clen)))
goto fail;
}
if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK))
h1m.flags |= H1_MF_XFER_LEN;
/* Update HTX start-line flags */
if (h1m.flags & H1_MF_XFER_ENC)
flags |= HTX_SL_F_XFER_ENC;
if (h1m.flags & H1_MF_XFER_LEN) {
flags |= HTX_SL_F_XFER_LEN;
if (h1m.flags & H1_MF_CHNK)
flags |= HTX_SL_F_CHNK;
else if (h1m.flags & H1_MF_CLEN)
flags |= HTX_SL_F_CLEN;
if (h1m.body_len == 0)
flags |= HTX_SL_F_BODYLESS;
}
sl->flags |= flags;
if (!htx_add_endof(htx, HTX_BLK_EOH) ||
(body_len && !htx_add_data_atonce(htx, ist2(body, body_len))) ||
!htx_add_endof(htx, HTX_BLK_EOM))
goto fail;
/* Now, forward the response and terminate the transaction */
s->txn->status = code;
htx_to_buf(htx, &s->res.buf);
if (!http_forward_proxy_resp(s, 1))
goto fail;
return 1;
fail:
channel_htx_truncate(&s->res, htx);
return 0;
}
/* Terminate a transaction if called from a lua action. For TCP streams,
* processing is just aborted. Nothing is returned to the client and all
* arguments are ignored. For HTTP streams, if a reply is passed as argument, it
* is forwarded to the client before terminating the transaction. On success,
* the function exits with ACT_RET_DONE code. If an error occurred, it exits
* with ACT_RET_ERR code. If this function is not called from a lua action, it
* just exits without any processing.
*/
__LJMP static int hlua_txn_done(lua_State *L)
{
struct hlua_txn *htxn;
struct channel *ic, *oc;
struct stream *s;
int finst;
MAY_LJMP(check_args(L, 1, "close"));
htxn = MAY_LJMP(hlua_checktxn(L, 1));
/* If the flags NOTERM is set, we cannot terminate the http
* session, so we just end the execution of the current
* lua code.
*/
if (htxn->flags & HLUA_TXN_NOTERM) {
/* If the flags NOTERM is set, we cannot terminate the session, so we
* just end the execution of the current lua code. */
if (htxn->flags & HLUA_TXN_NOTERM)
WILL_LJMP(hlua_done(L));
return 0;
s = htxn->s;
if (!(htxn->flags & HLUA_TXN_HTTP_RDY)) {
struct channel *req = &s->req;
struct channel *res = &s->res;
channel_auto_read(req);
channel_abort(req);
channel_auto_close(req);
channel_erase(req);
res->wex = tick_add_ifset(now_ms, res->wto);
channel_auto_read(res);
channel_auto_close(res);
channel_shutr_now(res);
finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_D);
goto done;
}
ic = &htxn->s->req;
oc = &htxn->s->res;
if (IS_HTX_STRM(htxn->s)) {
htxn->s->txn->status = 0;
http_reply_and_close(htxn->s, 0, NULL);
if (lua_gettop(L) == 1 || !lua_istable(L, 2)) {
/* No reply or invalid reply */
s->txn->status = 0;
http_reply_and_close(s, 0, NULL);
}
else {
channel_auto_read(ic);
channel_abort(ic);
channel_auto_close(ic);
channel_erase(ic);
oc->wex = tick_add_ifset(now_ms, oc->wto);
channel_auto_read(oc);
channel_auto_close(oc);
channel_shutr_now(oc);
/* Remove extra args to have the reply on top of the stack */
if (lua_gettop(L) > 2)
lua_pop(L, lua_gettop(L) - 2);
if (!hlua_txn_forward_reply(L, s)) {
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
lua_pushinteger(L, ACT_RET_ERR);
WILL_LJMP(hlua_done(L));
return 0; /* Never reached */
}
}
ic->analysers &= AN_REQ_FLT_END;
oc->analysers &= AN_RES_FLT_END;
finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_H);
if (htxn->dir == SMP_OPT_DIR_REQ) {
/* let's log the request time */
s->logs.tv_request = now;
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
_HA_ATOMIC_ADD(&s->sess->fe->fe_counters.intercepted_req, 1);
}
if (!(htxn->s->flags & SF_ERR_MASK)) // this is not really an error but it is
htxn->s->flags |= SF_ERR_LOCAL; // to mark that it comes from the proxy
done:
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL;
if (!(s->flags & SF_FINST_MASK))
s->flags |= finst;
lua_pushinteger(L, ACT_RET_DONE);
WILL_LJMP(hlua_done(L));
return 0;
}
/*
*
*
* Class REPLY
*
*
*/
/* Pushes the TXN reply onto the top of the stack. If the stask does not have a
* free slots, the function fails and returns 0;
*/
static int hlua_txn_reply_new(lua_State *L)
{
struct hlua_txn *htxn;
const char *reason, *body = NULL;
int ret, status;
htxn = MAY_LJMP(hlua_checktxn(L, 1));
if (!(htxn->flags & HLUA_TXN_HTTP_RDY)) {
hlua_pusherror(L, "txn object is not an HTTP transaction.");
WILL_LJMP(lua_error(L));
}
/* Default value */
status = 200;
reason = http_get_reason(status);
if (lua_istable(L, 2)) {
/* load status and reason from the table argument at index 2 */
ret = lua_getfield(L, 2, "status");
if (ret == LUA_TNIL)
goto reason;
else if (ret != LUA_TNUMBER) {
/* invalid status: ignore the reason */
goto body;
}
status = lua_tointeger(L, -1);
reason:
lua_pop(L, 1); /* restore the stack: remove status */
ret = lua_getfield(L, 2, "reason");
if (ret == LUA_TSTRING)
reason = lua_tostring(L, -1);
body:
lua_pop(L, 1); /* restore the stack: remove invalid status or reason */
ret = lua_getfield(L, 2, "body");
if (ret == LUA_TSTRING)
body = lua_tostring(L, -1);
lua_pop(L, 1); /* restore the stack: remove body */
}
/* Create the Reply table */
lua_newtable(L);
/* Add status element */
lua_pushstring(L, "status");
lua_pushinteger(L, status);
lua_settable(L, -3);
/* Add reason element */
reason = http_get_reason(status);
lua_pushstring(L, "reason");
lua_pushstring(L, reason);
lua_settable(L, -3);
/* Add body element, nil if undefined */
lua_pushstring(L, "body");
if (body)
lua_pushstring(L, body);
else
lua_pushnil(L);
lua_settable(L, -3);
/* Add headers element */
lua_pushstring(L, "headers");
lua_newtable(L);
/* stack: [ txn, <Arg:table>, <Reply:table>, "headers", <headers:table> ] */
if (lua_istable(L, 2)) {
/* load headers from the table argument at index 2. If it is a table, copy it. */
ret = lua_getfield(L, 2, "headers");
if (ret == LUA_TTABLE) {
/* stack: [ ... <headers:table>, <table> ] */
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
/* stack: [ ... <headers:table>, <table>, k, v] */
if (!lua_isstring(L, -1) && !lua_istable(L, -1)) {
/* invalid value type, skip it */
lua_pop(L, 1);
continue;
}
/* Duplicate the key and swap it with the value. */
lua_pushvalue(L, -2);
lua_insert(L, -2);
/* stack: [ ... <headers:table>, <table>, k, k, v ] */
lua_newtable(L);
lua_insert(L, -2);
/* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, v ] */
if (lua_isstring(L, -1)) {
/* push the value in the inner table */
lua_rawseti(L, -2, 1);
}
else { /* table */
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
/* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, <v:table>, k2, v2 ] */
if (!lua_isstring(L, -1)) {
/* invalid value type, skip it*/
lua_pop(L, 1);
continue;
}
/* push the value in the inner table */
lua_rawseti(L, -4, lua_rawlen(L, -4) + 1);
/* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, <v:table>, k2 ] */
}
lua_pop(L, 1);
/* stack: [ ... <headers:table>, <table>, k, k, <inner:table> ] */
}
/* push (k,v) on the stack in the headers table:
* stack: [ ... <headers:table>, <table>, k, k, v ]
*/
lua_settable(L, -5);
/* stack: [ ... <headers:table>, <table>, k ] */
}
}
lua_pop(L, 1);
}
/* stack: [ txn, <Arg:table>, <Reply:table>, "headers", <headers:table> ] */
lua_settable(L, -3);
/* stack: [ txn, <Arg:table>, <Reply:table> ] */
/* Pop a class sesison metatable and affect it to the userdata. */
lua_rawgeti(L, LUA_REGISTRYINDEX, class_txn_reply_ref);
lua_setmetatable(L, -2);
return 1;
}
/* Set the reply status code, and optionally the reason. If no reason is
* provided, the default one corresponding to the status code is used.
*/
__LJMP static int hlua_txn_reply_set_status(lua_State *L)
{
int status = MAY_LJMP(luaL_checkinteger(L, 2));
const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL));
/* First argument (self) must be a table */
luaL_checktype(L, 1, LUA_TTABLE);
if (status < 100 || status > 599) {
lua_pushboolean(L, 0);
return 1;
}
if (!reason)
reason = http_get_reason(status);
lua_pushinteger(L, status);
lua_setfield(L, 1, "status");
lua_pushstring(L, reason);
lua_setfield(L, 1, "reason");
lua_pushboolean(L, 1);
return 1;
}
/* Add a header into the reply object. Each header name is associated to an
* array of values in the "headers" table. If the header name is not found, a
* new entry is created.
*/
__LJMP static int hlua_txn_reply_add_header(lua_State *L)
{
const char *name = MAY_LJMP(luaL_checkstring(L, 2));
const char *value = MAY_LJMP(luaL_checkstring(L, 3));
int ret;
/* First argument (self) must be a table */
luaL_checktype(L, 1, LUA_TTABLE);
/* Push in the stack the "headers" entry. */
ret = lua_getfield(L, 1, "headers");
if (ret != LUA_TTABLE) {
hlua_pusherror(L, "Reply['headers'] is expected to a an array. %s found", lua_typename(L, ret));
WILL_LJMP(lua_error(L));
}
/* check if the header is already registered. If not, register it. */
ret = lua_getfield(L, -1, name);
if (ret == LUA_TNIL) {
/* Entry not found. */
lua_pop(L, 1); /* remove the nil. The "headers" table is the top of the stack. */
/* Insert the new header name in the array in the top of the stack.
* It left the new array in the top of the stack.
*/
lua_newtable(L);
lua_pushstring(L, name);
lua_pushvalue(L, -2);
lua_settable(L, -4);
}
else if (ret != LUA_TTABLE) {
hlua_pusherror(L, "Reply['headers']['%s'] is expected to be an array. %s found", name, lua_typename(L, ret));
WILL_LJMP(lua_error(L));
}
/* Now the top od thestack is an array of values. We push
* the header value as new entry.
*/
lua_pushstring(L, value);
ret = lua_rawlen(L, -2);
lua_rawseti(L, -2, ret + 1);
lua_pushboolean(L, 1);
return 1;
}
/* Remove all occurrences of a given header name. */
__LJMP static int hlua_txn_reply_del_header(lua_State *L)
{
const char *name = MAY_LJMP(luaL_checkstring(L, 2));
int ret;
/* First argument (self) must be a table */
luaL_checktype(L, 1, LUA_TTABLE);
/* Push in the stack the "headers" entry. */
ret = lua_getfield(L, 1, "headers");
if (ret != LUA_TTABLE) {
hlua_pusherror(L, "Reply['headers'] is expected to be an array. %s found", lua_typename(L, ret));
WILL_LJMP(lua_error(L));
}
lua_pushstring(L, name);
lua_pushnil(L);
lua_settable(L, -3);
lua_pushboolean(L, 1);
return 1;
}
/* Set the reply's body. Overwrite any existing entry. */
__LJMP static int hlua_txn_reply_set_body(lua_State *L)
{
const char *payload = MAY_LJMP(luaL_checkstring(L, 2));
/* First argument (self) must be a table */
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushstring(L, payload);
lua_setfield(L, 1, "body");
lua_pushboolean(L, 1);
return 1;
}
__LJMP static int hlua_log(lua_State *L)
{
int level;
@ -7954,6 +8393,7 @@ void hlua_init(void)
hlua_class_function(gL.T, "unset_var", hlua_unset_var);
hlua_class_function(gL.T, "get_var", hlua_get_var);
hlua_class_function(gL.T, "done", hlua_txn_done);
hlua_class_function(gL.T, "reply", hlua_txn_reply_new);
hlua_class_function(gL.T, "set_loglevel", hlua_txn_set_loglevel);
hlua_class_function(gL.T, "set_tos", hlua_txn_set_tos);
hlua_class_function(gL.T, "set_mark", hlua_txn_set_mark);
@ -7971,6 +8411,22 @@ void hlua_init(void)
/* Register previous table in the registry with reference and named entry. */
class_txn_ref = hlua_register_metatable(gL.T, CLASS_TXN);
/*
*
* Register class reply
*
*/
lua_newtable(gL.T);
lua_pushstring(gL.T, "__index");
lua_newtable(gL.T);
hlua_class_function(gL.T, "set_status", hlua_txn_reply_set_status);
hlua_class_function(gL.T, "add_header", hlua_txn_reply_add_header);
hlua_class_function(gL.T, "del_header", hlua_txn_reply_del_header);
hlua_class_function(gL.T, "set_body", hlua_txn_reply_set_body);
lua_settable(gL.T, -3); /* Sets the __index entry. */
class_txn_reply_ref = luaL_ref(gL.T, LUA_REGISTRYINDEX);
/*
*
* Register class Socket