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:
parent
2c2c2e381b
commit
700d9e88ad
@ -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>`_
|
||||
|
||||
|
516
src/hlua.c
516
src/hlua.c
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user