MINOR: lua: add AppletHTTP class and service
This class is used by Lua code for running as an applet called in HTTP mode
It defines also the associated lua service
diff --git a/include/types/applet.h b/include/types/applet.h
index 2254b0a..2e1626a 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -121,6 +121,13 @@
struct task *task;
} hlua_apptcp;
struct {
+ struct hlua hlua;
+ int left_bytes; /* The max amount of bytes that we can read. */
+ int flags;
+ int status;
+ struct task *task;
+ } hlua_apphttp;
+ struct {
struct dns_resolvers *ptr;
} resolvers;
struct {
diff --git a/include/types/hlua.h b/include/types/hlua.h
index eccfc6c..ac56c2a 100644
--- a/include/types/hlua.h
+++ b/include/types/hlua.h
@@ -18,6 +18,7 @@
#define CLASS_HTTP "HTTP"
#define CLASS_MAP "Map"
#define CLASS_APPLET_TCP "AppletTCP"
+#define CLASS_APPLET_HTTP "AppletHTTP"
struct stream;
diff --git a/src/hlua.c b/src/hlua.c
index 36bd100..12ffe75 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -110,8 +110,13 @@
} while(0)
/* Applet status flags */
-#define APPLET_DONE 0x1 /* applet processing is done. */
-#define APPLET_SENT 0x2 /* some data are sent. */
+#define APPLET_DONE 0x01 /* applet processing is done. */
+#define APPLET_100C 0x02 /* 100 continue expected. */
+#define APPLET_HDR_SENT 0x04 /* Response header sent. */
+#define APPLET_CHUNKED 0x08 /* Use transfer encoding chunked. */
+#define APPLET_LAST_CHK 0x10 /* Last chunk sent. */
+
+#define HTTP_100C "HTTP/1.1 100 Continue\r\n\r\n"
/* The main Lua execution context. */
struct hlua gL;
@@ -143,6 +148,7 @@
static int class_http_ref;
static int class_map_ref;
static int class_applet_tcp_ref;
+static int class_applet_http_ref;
/* Global Lua execution timeout. By default Lua, execution linked
* with stream (actions, sample-fetches and converters) have a
@@ -190,6 +196,14 @@
static struct hlua_mem_allocator hlua_global_allocator;
+static const char error_500[] =
+ "HTTP/1.0 500 Server Error\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<html><body><h1>500 Server Error</h1>\nAn internal server error occured.\n</body></html>\n";
+
/* These functions converts types between HAProxy internal args or
* sample and LUA types. Another function permits to check if the
* LUA stack contains arguments according with an required ARG_T
@@ -203,6 +217,8 @@
static int hlua_smp2lua_str(lua_State *L, struct sample *smp);
static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp);
+__LJMP static int hlua_http_get_headers(lua_State *L, struct hlua_txn *htxn, struct http_msg *msg);
+
#define SEND_ERR(__be, __fmt, __args...) \
do { \
send_log(__be, LOG_ERR, __fmt, ## __args); \
@@ -224,6 +240,16 @@
return luaL_ref(L, LUA_REGISTRYINDEX);
}
+/* Return the string that is of the top of the stack. */
+const char *hlua_get_top_error_string(lua_State *L)
+{
+ if (lua_gettop(L) < 1)
+ return "unknown error";
+ if (lua_type(L, -1) != LUA_TSTRING)
+ return "unknown error";
+ return lua_tostring(L, -1);
+}
+
/* The three following functions are useful for adding entries
* in a table. These functions takes a string and respectively an
* integer, a string or a function and add it to the table in the
@@ -3424,7 +3450,7 @@
/*
*
*
- * Class HTTP
+ * Class AppletHTTP
*
*
*/
@@ -3432,17 +3458,24 @@
/* Returns a struct hlua_txn if the stack entry "ud" is
* a class stream, otherwise it throws an error.
*/
-__LJMP static struct hlua_txn *hlua_checkhttp(lua_State *L, int ud)
+__LJMP static struct hlua_appctx *hlua_checkapplet_http(lua_State *L, int ud)
{
- return (struct hlua_txn *)MAY_LJMP(hlua_checkudata(L, ud, class_http_ref));
+ return (struct hlua_appctx *)MAY_LJMP(hlua_checkudata(L, ud, class_applet_http_ref));
}
-/* This function creates and push in the stack a HTTP object
+/* This function creates and push in the stack an Applet object
* according with a current TXN.
*/
-static int hlua_http_new(lua_State *L, struct hlua_txn *txn)
+static int hlua_applet_http_new(lua_State *L, struct appctx *ctx)
{
- struct hlua_txn *htxn;
+ struct hlua_appctx *appctx;
+ struct stream_interface *si = ctx->owner;
+ struct stream *s = si_strm(si);
+ struct proxy *px = s->be;
+ struct http_txn *txn = s->txn;
+ const char *path;
+ const char *end;
+ const char *p;
/* Check stack size. */
if (!lua_checkstack(L, 3))
@@ -3453,195 +3486,816 @@
* same than the TXN object.
*/
lua_newtable(L);
- htxn = lua_newuserdata(L, sizeof(*htxn));
+ appctx = lua_newuserdata(L, sizeof(*appctx));
lua_rawseti(L, -2, 0);
-
- htxn->s = txn->s;
- htxn->p = txn->p;
-
- /* Pop a class stream metatable and affect it to the table. */
- lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_ref);
- lua_setmetatable(L, -2);
+ appctx->appctx = ctx;
+ appctx->appctx->ctx.hlua_apphttp.status = 200; /* Default status code returned. */
+ appctx->htxn.s = s;
+ appctx->htxn.p = px;
- return 1;
-}
+ /* Create the "f" field that contains a list of fetches. */
+ lua_pushstring(L, "f");
+ if (!hlua_fetches_new(L, &appctx->htxn, 0))
+ return 0;
+ lua_settable(L, -3);
-/* This function creates ans returns an array of HTTP headers.
- * This function does not fails. It is used as wrapper with the
- * 2 following functions.
- */
-__LJMP static int hlua_http_get_headers(lua_State *L, struct hlua_txn *htxn, struct http_msg *msg)
-{
- const char *cur_ptr, *cur_next, *p;
- int old_idx, cur_idx;
- struct hdr_idx_elem *cur_hdr;
- const char *hn, *hv;
- int hnl, hvl;
- int type;
- const char *in;
- char *out;
- int len;
+ /* Create the "sf" field that contains a list of stringsafe fetches. */
+ lua_pushstring(L, "sf");
+ if (!hlua_fetches_new(L, &appctx->htxn, 1))
+ return 0;
+ lua_settable(L, -3);
- /* Create the table. */
- lua_newtable(L);
+ /* Create the "c" field that contains a list of converters. */
+ lua_pushstring(L, "c");
+ if (!hlua_converters_new(L, &appctx->htxn, 0))
+ return 0;
+ lua_settable(L, -3);
- if (!htxn->s->txn)
- return 1;
+ /* Create the "sc" field that contains a list of stringsafe converters. */
+ lua_pushstring(L, "sc");
+ if (!hlua_converters_new(L, &appctx->htxn, 1))
+ return 0;
+ lua_settable(L, -3);
- /* Build array of headers. */
- old_idx = 0;
- cur_next = msg->chn->buf->p + hdr_idx_first_pos(&htxn->s->txn->hdr_idx);
+ /* Stores the request method. */
+ lua_pushstring(L, "method");
+ lua_pushlstring(L, txn->req.chn->buf->p, txn->req.sl.rq.m_l);
+ lua_settable(L, -3);
- while (1) {
- cur_idx = htxn->s->txn->hdr_idx.v[old_idx].next;
- if (!cur_idx)
- break;
- old_idx = cur_idx;
+ /* Stores the http version. */
+ lua_pushstring(L, "version");
+ lua_pushlstring(L, txn->req.chn->buf->p + txn->req.sl.rq.v, txn->req.sl.rq.v_l);
+ lua_settable(L, -3);
- cur_hdr = &htxn->s->txn->hdr_idx.v[cur_idx];
- cur_ptr = cur_next;
- cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1;
+ /* Get path and qs */
+ path = http_get_path(txn);
+ end = txn->req.chn->buf->p + txn->req.sl.rq.u + txn->req.sl.rq.u_l;
+ p = path;
+ while (p < end && *p != '?')
+ p++;
- /* Now we have one full header at cur_ptr of len cur_hdr->len,
- * and the next header starts at cur_next. We'll check
- * this header in the list as well as against the default
- * rule.
- */
+ /* Stores the request path. */
+ lua_pushstring(L, "path");
+ lua_pushlstring(L, path, p - path);
+ lua_settable(L, -3);
- /* look for ': *'. */
- hn = cur_ptr;
- for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++);
- if (p >= cur_ptr+cur_hdr->len)
- continue;
- hnl = p - hn;
+ /* Stores the query string. */
+ lua_pushstring(L, "qs");
+ if (*p == '?')
p++;
- while (p < cur_ptr+cur_hdr->len && ( *p == ' ' || *p == '\t' ))
- p++;
- if (p >= cur_ptr+cur_hdr->len)
- continue;
- hv = p;
- hvl = cur_ptr+cur_hdr->len-p;
-
- /* Lowercase the key. Don't check the size of trash, it have
- * the size of one buffer and the input data contains in one
- * buffer.
- */
- out = trash.str;
- for (in=hn; in<hn+hnl; in++, out++)
- *out = tolower(*in);
- *out = '\0';
+ lua_pushlstring(L, p, end - p);
+ lua_settable(L, -3);
- /* Check for existing entry:
- * assume that the table is on the top of the stack, and
- * push the key in the stack, the function lua_gettable()
- * perform the lookup.
- */
- lua_pushlstring(L, trash.str, hnl);
- lua_gettable(L, -2);
- type = lua_type(L, -1);
+ /* Stores the request path. */
+ lua_pushstring(L, "length");
+ lua_pushinteger(L, txn->req.body_len);
+ lua_settable(L, -3);
- switch (type) {
- case LUA_TNIL:
- /* Table not found, create it. */
- lua_pop(L, 1); /* remove the nil value. */
- lua_pushlstring(L, trash.str, hnl); /* push the header name as key. */
- lua_newtable(L); /* create and push empty table. */
- lua_pushlstring(L, hv, hvl); /* push header value. */
- lua_rawseti(L, -2, 0); /* index header value (pop it). */
- lua_rawset(L, -3); /* index new table with header name (pop the values). */
- break;
+ /* Create an array of HTTP request headers. */
+ lua_pushstring(L, "headers");
+ MAY_LJMP(hlua_http_get_headers(L, &appctx->htxn, &appctx->htxn.s->txn->req));
+ lua_settable(L, -3);
- case LUA_TTABLE:
- /* Entry found: push the value in the table. */
- len = lua_rawlen(L, -1);
- lua_pushlstring(L, hv, hvl); /* push header value. */
- lua_rawseti(L, -2, len+1); /* index header value (pop it). */
- lua_pop(L, 1); /* remove the table (it is stored in the main table). */
- break;
+ /* Create an empty array of HTTP request headers. */
+ lua_pushstring(L, "response");
+ lua_newtable(L);
+ lua_settable(L, -3);
- default:
- /* Other cases are errors. */
- hlua_pusherror(L, "internal error during the parsing of headers.");
- WILL_LJMP(lua_error(L));
- }
- }
+ /* Pop a class stream metatable and affect it to the table. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_applet_http_ref);
+ lua_setmetatable(L, -2);
return 1;
}
-__LJMP static int hlua_http_req_get_headers(lua_State *L)
+/* If expected data not yet available, it returns a yield. This function
+ * consumes the data in the buffer. It returns a string containing the
+ * data. This string can be empty.
+ */
+__LJMP static int hlua_applet_http_getline_yield(lua_State *L, int status, lua_KContext ctx)
{
- struct hlua_txn *htxn;
-
- MAY_LJMP(check_args(L, 1, "req_get_headers"));
- htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-
- return hlua_http_get_headers(L, htxn, &htxn->s->txn->req);
-}
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct stream_interface *si = appctx->appctx->owner;
+ struct channel *chn = si_ic(si);
+ int ret;
+ char *blk1;
+ int len1;
+ char *blk2;
+ int len2;
-__LJMP static int hlua_http_res_get_headers(lua_State *L)
-{
- struct hlua_txn *htxn;
+ /* Maybe we cant send a 100-continue ? */
+ if (appctx->appctx->ctx.hlua_apphttp.flags & APPLET_100C) {
+ ret = bi_putblk(chn, HTTP_100C, strlen(HTTP_100C));
+ /* if ret == -2 or -3 the channel closed or the message si too
+ * big for the buffers. We cant send anything. So, we ignoring
+ * the error, considers that the 100-continue is sent, and try
+ * to receive.
+ * If ret is -1, we dont have room in the buffer, so we yield.
+ */
+ if (ret == -1) {
+ si_applet_cant_put(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_getline_yield, TICK_ETERNITY, 0));
+ }
+ appctx->appctx->ctx.hlua_apphttp.flags &= ~APPLET_100C;
+ }
- MAY_LJMP(check_args(L, 1, "res_get_headers"));
- htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+ /* Check for the end of the data. */
+ if (appctx->appctx->ctx.hlua_apphttp.left_bytes <= 0) {
+ luaL_pushresult(&appctx->b);
+ return 1;
+ }
- return hlua_http_get_headers(L, htxn, &htxn->s->txn->rsp);
-}
+ /* Read the maximum amount of data avalaible. */
+ ret = bo_getline_nc(si_oc(si), &blk1, &len1, &blk2, &len2);
-/* This function replace full header, or just a value in
- * the request or in the response. It is a wrapper fir the
- * 4 following functions.
- */
-__LJMP static inline int hlua_http_rep_hdr(lua_State *L, struct hlua_txn *htxn,
- struct http_msg *msg, int action)
-{
- size_t name_len;
- const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
- const char *reg = MAY_LJMP(luaL_checkstring(L, 3));
- const char *value = MAY_LJMP(luaL_checkstring(L, 4));
- struct my_regex re;
+ /* Data not yet avalaible. return yield. */
+ if (ret == 0) {
+ si_applet_cant_get(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_getline_yield, TICK_ETERNITY, 0));
+ }
- if (!regex_comp(reg, &re, 1, 1, NULL))
- WILL_LJMP(luaL_argerror(L, 3, "invalid regex"));
+ /* End of data: commit the total strings and return. */
+ if (ret < 0) {
+ luaL_pushresult(&appctx->b);
+ return 1;
+ }
- http_transform_header_str(htxn->s, msg, name, name_len, value, &re, action);
- regex_free(&re);
- return 0;
-}
+ /* Ensure that the block 2 length is usable. */
+ if (ret == 1)
+ len2 = 0;
-__LJMP static int hlua_http_req_rep_hdr(lua_State *L)
-{
- struct hlua_txn *htxn;
+ /* Copy the fisrt block caping to the length required. */
+ if (len1 > appctx->appctx->ctx.hlua_apphttp.left_bytes)
+ len1 = appctx->appctx->ctx.hlua_apphttp.left_bytes;
+ luaL_addlstring(&appctx->b, blk1, len1);
+ appctx->appctx->ctx.hlua_apphttp.left_bytes -= len1;
- MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
- htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+ /* Copy the second block. */
+ if (len2 > appctx->appctx->ctx.hlua_apphttp.left_bytes)
+ len2 = appctx->appctx->ctx.hlua_apphttp.left_bytes;
+ luaL_addlstring(&appctx->b, blk2, len2);
+ appctx->appctx->ctx.hlua_apphttp.left_bytes -= len2;
- return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, ACT_HTTP_REPLACE_HDR));
+ /* Consume input channel output buffer data. */
+ bo_skip(si_oc(si), len1 + len2);
+ luaL_pushresult(&appctx->b);
+ return 1;
}
-__LJMP static int hlua_http_res_rep_hdr(lua_State *L)
+/* Check arguments for the fucntion "hlua_channel_get_yield". */
+__LJMP static int hlua_applet_http_getline(lua_State *L)
{
- struct hlua_txn *htxn;
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
- MAY_LJMP(check_args(L, 4, "res_rep_hdr"));
- htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+ /* Initialise the string catenation. */
+ luaL_buffinit(L, &appctx->b);
- return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->rsp, ACT_HTTP_REPLACE_HDR));
+ return MAY_LJMP(hlua_applet_http_getline_yield(L, 0, 0));
}
-__LJMP static int hlua_http_req_rep_val(lua_State *L)
+/* If expected data not yet available, it returns a yield. This function
+ * consumes the data in the buffer. It returns a string containing the
+ * data. This string can be empty.
+ */
+__LJMP static int hlua_applet_http_recv_yield(lua_State *L, int status, lua_KContext ctx)
{
- struct hlua_txn *htxn;
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct stream_interface *si = appctx->appctx->owner;
+ int len = MAY_LJMP(luaL_checkinteger(L, 2));
+ struct channel *chn = si_ic(si);
+ int ret;
+ char *blk1;
+ int len1;
+ char *blk2;
+ int len2;
- MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
- htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+ /* Maybe we cant send a 100-continue ? */
+ if (appctx->appctx->ctx.hlua_apphttp.flags & APPLET_100C) {
+ ret = bi_putblk(chn, HTTP_100C, strlen(HTTP_100C));
+ /* if ret == -2 or -3 the channel closed or the message si too
+ * big for the buffers. We cant send anything. So, we ignoring
+ * the error, considers that the 100-continue is sent, and try
+ * to receive.
+ * If ret is -1, we dont have room in the buffer, so we yield.
+ */
+ if (ret == -1) {
+ si_applet_cant_put(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_recv_yield, TICK_ETERNITY, 0));
+ }
+ appctx->appctx->ctx.hlua_apphttp.flags &= ~APPLET_100C;
+ }
- return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, ACT_HTTP_REPLACE_VAL));
-}
+ /* Read the maximum amount of data avalaible. */
+ ret = bo_getblk_nc(si_oc(si), &blk1, &len1, &blk2, &len2);
-__LJMP static int hlua_http_res_rep_val(lua_State *L)
-{
+ /* Data not yet avalaible. return yield. */
+ if (ret == 0) {
+ si_applet_cant_get(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_recv_yield, TICK_ETERNITY, 0));
+ }
+
+ /* End of data: commit the total strings and return. */
+ if (ret < 0) {
+ luaL_pushresult(&appctx->b);
+ return 1;
+ }
+
+ /* Ensure that the block 2 length is usable. */
+ if (ret == 1)
+ len2 = 0;
+
+ /* Copy the fisrt block caping to the length required. */
+ if (len1 > len)
+ len1 = len;
+ luaL_addlstring(&appctx->b, blk1, len1);
+ len -= len1;
+
+ /* Copy the second block. */
+ if (len2 > len)
+ len2 = len;
+ luaL_addlstring(&appctx->b, blk2, len2);
+ len -= len2;
+
+ /* Consume input channel output buffer data. */
+ bo_skip(si_oc(si), len1 + len2);
+ if (appctx->appctx->ctx.hlua_apphttp.left_bytes != -1)
+ appctx->appctx->ctx.hlua_apphttp.left_bytes -= len;
+
+ /* If we are no other data avalaible, yield waiting for new data. */
+ if (len > 0) {
+ lua_pushinteger(L, len);
+ lua_replace(L, 2);
+ si_applet_cant_get(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_recv_yield, TICK_ETERNITY, 0));
+ }
+
+ /* return the result. */
+ luaL_pushresult(&appctx->b);
+ return 1;
+}
+
+/* Check arguments for the fucntion "hlua_channel_get_yield". */
+__LJMP static int hlua_applet_http_recv(lua_State *L)
+{
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ int len = -1;
+
+ /* Check arguments. */
+ if (lua_gettop(L) > 2)
+ WILL_LJMP(luaL_error(L, "The 'recv' function requires between 1 and 2 arguments."));
+ if (lua_gettop(L) >= 2) {
+ len = MAY_LJMP(luaL_checkinteger(L, 2));
+ lua_pop(L, 1);
+ }
+
+ /* Check the required length */
+ if (len == -1 || len > appctx->appctx->ctx.hlua_apphttp.left_bytes)
+ len = appctx->appctx->ctx.hlua_apphttp.left_bytes;
+ lua_pushinteger(L, len);
+
+ /* Initialise the string catenation. */
+ luaL_buffinit(L, &appctx->b);
+
+ return MAY_LJMP(hlua_applet_http_recv_yield(L, 0, 0));
+}
+
+/* Append data in the output side of the buffer. This data is immediatly
+ * sent. The fcuntion returns the ammount of data writed. If the buffer
+ * cannot contains the data, the function yield. The function returns -1
+ * if the channel is closed.
+ */
+__LJMP static int hlua_applet_http_send_yield(lua_State *L, int status, lua_KContext ctx)
+{
+ size_t len;
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ const char *str = MAY_LJMP(luaL_checklstring(L, 2, &len));
+ int l = MAY_LJMP(luaL_checkinteger(L, 3));
+ struct stream_interface *si = appctx->appctx->owner;
+ struct channel *chn = si_ic(si);
+ int max;
+
+ /* Get the max amount of data which can write as input in the channel. */
+ max = channel_recv_max(chn);
+ if (max > (len - l))
+ max = len - l;
+
+ /* Copy data. */
+ bi_putblk(chn, str + l, max);
+
+ /* update counters. */
+ l += max;
+ lua_pop(L, 1);
+ lua_pushinteger(L, l);
+
+ /* If some data is not send, declares the situation to the
+ * applet, and returns a yield.
+ */
+ if (l < len) {
+ si_applet_cant_put(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_send_yield, TICK_ETERNITY, 0));
+ }
+
+ return 1;
+}
+
+/* Just a wraper of "hlua_applet_send_yield". This wrapper permits
+ * yield the LUA process, and resume it without checking the
+ * input arguments.
+ */
+__LJMP static int hlua_applet_http_send(lua_State *L)
+{
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ size_t len;
+ char hex[10];
+
+ MAY_LJMP(luaL_checklstring(L, 2, &len));
+
+ /* If transfer encoding chunked is selected, we surround the data
+ * by chunk data.
+ */
+ if (appctx->appctx->ctx.hlua_apphttp.flags & APPLET_CHUNKED) {
+ snprintf(hex, 9, "%x", (unsigned int)len);
+ lua_pushfstring(L, "%s\r\n", hex);
+ lua_insert(L, 2); /* swap the last 2 entries. */
+ lua_pushstring(L, "\r\n");
+ lua_concat(L, 3);
+ }
+
+ /* This interger is used for followinf the amount of data sent. */
+ lua_pushinteger(L, 0);
+
+ /* We want to send some data. Headers must be sent. */
+ if (!(appctx->appctx->ctx.hlua_apphttp.flags & APPLET_HDR_SENT)) {
+ hlua_pusherror(L, "Lua: 'send' you must call start_response() before sending data.");
+ WILL_LJMP(lua_error(L));
+ }
+
+ return MAY_LJMP(hlua_applet_http_send_yield(L, 0, 0));
+}
+
+__LJMP static int hlua_applet_http_addheader(lua_State *L)
+{
+ const char *name;
+ int ret;
+
+ MAY_LJMP(hlua_checkapplet_http(L, 1));
+ name = MAY_LJMP(luaL_checkstring(L, 2));
+ MAY_LJMP(luaL_checkstring(L, 3));
+
+ /* Push in the stack the "response" entry. */
+ ret = lua_getfield(L, 1, "response");
+ if (ret != LUA_TTABLE) {
+ hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response'] "
+ "is expected as an array. %s found", lua_typename(L, ret));
+ WILL_LJMP(lua_error(L));
+ }
+
+ /* check if the header is already registered if it is not
+ * the case, register it.
+ */
+ ret = lua_getfield(L, -1, name);
+ if (ret == LUA_TNIL) {
+
+ /* Entry not found. */
+ lua_pop(L, 1); /* remove the nil. The "response" 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_pushvalue(L, 2);
+ lua_pushvalue(L, -2);
+ lua_settable(L, -4);
+
+ } else if (ret != LUA_TTABLE) {
+
+ /* corruption error. */
+ hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response']['%s'] "
+ "is expected as 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_pushvalue(L, 3);
+ ret = lua_rawlen(L, -2);
+ lua_rawseti(L, -2, ret + 1);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+__LJMP static int hlua_applet_http_status(lua_State *L)
+{
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ int status = MAY_LJMP(luaL_checkinteger(L, 2));
+
+ if (status < 100 || status > 599) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+
+ appctx->appctx->ctx.hlua_apphttp.status = status;
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+/* We will build the status line and the headers of the HTTP response.
+ * We will try send at once if its not possible, we give back the hand
+ * waiting for more room.
+ */
+__LJMP static int hlua_applet_http_start_response_yield(lua_State *L, int status, lua_KContext ctx)
+{
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct stream_interface *si = appctx->appctx->owner;
+ struct channel *chn = si_ic(si);
+ int ret;
+ size_t len;
+ const char *msg;
+
+ /* Get the message as the first argument on the stack. */
+ msg = MAY_LJMP(luaL_checklstring(L, 2, &len));
+
+ /* Send the message at once. */
+ ret = bi_putblk(chn, msg, len);
+
+ /* if ret == -2 or -3 the channel closed or the message si too
+ * big for the buffers.
+ */
+ if (ret == -2 || ret == -3) {
+ hlua_pusherror(L, "Lua: 'start_response': response header block too big");
+ WILL_LJMP(lua_error(L));
+ }
+
+ /* If ret is -1, we dont have room in the buffer, so we yield. */
+ if (ret == -1) {
+ si_applet_cant_put(si);
+ WILL_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_start_response_yield, TICK_ETERNITY, 0));
+ }
+
+ /* Headers sent, set the flag. */
+ appctx->appctx->ctx.hlua_apphttp.flags |= APPLET_HDR_SENT;
+ return 0;
+}
+
+__LJMP static int hlua_applet_http_start_response(lua_State *L)
+{
+ struct chunk *tmp = get_trash_chunk();
+ struct hlua_appctx *appctx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct stream_interface *si = appctx->appctx->owner;
+ struct stream *s = si_strm(si);
+ struct http_txn *txn = s->txn;
+ const char *name;
+ const char *value;
+ int id;
+ int hdr_connection = 0;
+ int hdr_contentlength = -1;
+ int hdr_chunked = 0;
+
+ /* Use the same http version than the request. */
+ chunk_appendf(tmp, "HTTP/1.%c %d %s\r\n",
+ txn->req.flags & HTTP_MSGF_VER_11 ? '1' : '0',
+ appctx->appctx->ctx.hlua_apphttp.status,
+ get_reason(appctx->appctx->ctx.hlua_apphttp.status));
+
+ /* Get the array associated to the field "response" in the object AppletHTTP. */
+ lua_pushvalue(L, 0);
+ if (lua_getfield(L, 1, "response") != LUA_TTABLE) {
+ hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'] missing.\n",
+ appctx->appctx->rule->arg.hlua_rule->fcn.name);
+ WILL_LJMP(lua_error(L));
+ }
+
+ /* Browse the list of headers. */
+ lua_pushnil(L);
+ while(lua_next(L, -2) != 0) {
+
+ /* We expect a string as -2. */
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'][] element must be a string. got %s.\n",
+ appctx->appctx->rule->arg.hlua_rule->fcn.name,
+ lua_typename(L, lua_type(L, -2)));
+ WILL_LJMP(lua_error(L));
+ }
+ name = lua_tostring(L, -2);
+
+ /* We expect an array as -1. */
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'] element must be an table. got %s.\n",
+ appctx->appctx->rule->arg.hlua_rule->fcn.name,
+ name,
+ lua_typename(L, lua_type(L, -1)));
+ WILL_LJMP(lua_error(L));
+ }
+
+ /* Browse the table who is on the top of the stack. */
+ lua_pushnil(L);
+ while(lua_next(L, -2) != 0) {
+
+ /* We expect a number as -2. */
+ if (lua_type(L, -2) != LUA_TNUMBER) {
+ hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][] element must be a number. got %s.\n",
+ appctx->appctx->rule->arg.hlua_rule->fcn.name,
+ name,
+ lua_typename(L, lua_type(L, -2)));
+ WILL_LJMP(lua_error(L));
+ }
+ id = lua_tointeger(L, -2);
+
+ /* We expect a string as -2. */
+ if (lua_type(L, -1) != LUA_TSTRING) {
+ hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][%d] element must be a string. got %s.\n",
+ appctx->appctx->rule->arg.hlua_rule->fcn.name,
+ name, id,
+ lua_typename(L, lua_type(L, -1)));
+ WILL_LJMP(lua_error(L));
+ }
+ value = lua_tostring(L, -1);
+
+ /* Catenate a new header. */
+ chunk_appendf(tmp, "%s: %s\r\n", name, value);
+
+ /* Protocol checks. */
+
+ /* Check if the header conneciton is present. */
+ if (strcasecmp("connection", name) == 0)
+ hdr_connection = 1;
+
+ /* Copy the header content length. The length conversion
+ * is done without control. If it contains a ad value, this
+ * is not our problem.
+ */
+ if (strcasecmp("content-length", name) == 0)
+ hdr_contentlength = atoi(value);
+
+ /* Check if the client annouces a transfer-encoding chunked it self. */
+ if (strcasecmp("transfer-encoding", name) == 0 &&
+ strcasecmp("chunked", value) == 0)
+ hdr_chunked = 1;
+
+ /* Remove the array from the stack, and get next element with a remaining string. */
+ lua_pop(L, 1);
+ }
+
+ /* Remove the array from the stack, and get next element with a remaining string. */
+ lua_pop(L, 1);
+ }
+
+ /* If the http protocol version is 1.1, we expect an header "connection" set
+ * to "close" to be HAProxy/keeplive compliant. Otherwise, we expect nothing.
+ * If the header conneciton is present, don't change it, if it is not present,
+ * we must set.
+ *
+ * we set a "connection: close" header for ensuring that the keepalive will be
+ * respected by haproxy. HAProcy considers that the application cloe the connection
+ * and it keep the connection from the client open.
+ */
+ if (txn->req.flags & HTTP_MSGF_VER_11 && !hdr_connection)
+ chunk_appendf(tmp, "Connection: close\r\n");
+
+ /* If we dont have a content-length set, we must announce a transfer enconding
+ * chunked. This is required by haproxy for the keepalive compliance.
+ * If the applet annouce a transfer-encoding chunked itslef, don't
+ * do anything.
+ */
+ if (hdr_contentlength == -1 && hdr_chunked == 0) {
+ chunk_appendf(tmp, "Transfer-encoding: chunked\r\n");
+ appctx->appctx->ctx.hlua_apphttp.flags |= APPLET_CHUNKED;
+ }
+
+ /* Finalize headers. */
+ chunk_appendf(tmp, "\r\n");
+
+ /* Remove the last entry and the array of headers */
+ lua_pop(L, 2);
+
+ /* Push the headers block. */
+ lua_pushlstring(L, tmp->str, tmp->len);
+
+ return MAY_LJMP(hlua_applet_http_start_response_yield(L, 0, 0));
+}
+
+/*
+ *
+ *
+ * Class HTTP
+ *
+ *
+ */
+
+/* Returns a struct hlua_txn if the stack entry "ud" is
+ * a class stream, otherwise it throws an error.
+ */
+__LJMP static struct hlua_txn *hlua_checkhttp(lua_State *L, int ud)
+{
+ return (struct hlua_txn *)MAY_LJMP(hlua_checkudata(L, ud, class_http_ref));
+}
+
+/* This function creates and push in the stack a HTTP object
+ * according with a current TXN.
+ */
+static int hlua_http_new(lua_State *L, struct hlua_txn *txn)
+{
+ struct hlua_txn *htxn;
+
+ /* Check stack size. */
+ if (!lua_checkstack(L, 3))
+ return 0;
+
+ /* Create the object: obj[0] = userdata.
+ * Note that the base of the Converters object is the
+ * same than the TXN object.
+ */
+ lua_newtable(L);
+ htxn = lua_newuserdata(L, sizeof(*htxn));
+ lua_rawseti(L, -2, 0);
+
+ htxn->s = txn->s;
+ htxn->p = txn->p;
+
+ /* Pop a class stream metatable and affect it to the table. */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_ref);
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
+
+/* This function creates ans returns an array of HTTP headers.
+ * This function does not fails. It is used as wrapper with the
+ * 2 following functions.
+ */
+__LJMP static int hlua_http_get_headers(lua_State *L, struct hlua_txn *htxn, struct http_msg *msg)
+{
+ const char *cur_ptr, *cur_next, *p;
+ int old_idx, cur_idx;
+ struct hdr_idx_elem *cur_hdr;
+ const char *hn, *hv;
+ int hnl, hvl;
+ int type;
+ const char *in;
+ char *out;
+ int len;
+
+ /* Create the table. */
+ lua_newtable(L);
+
+ if (!htxn->s->txn)
+ return 1;
+
+ /* Build array of headers. */
+ old_idx = 0;
+ cur_next = msg->chn->buf->p + hdr_idx_first_pos(&htxn->s->txn->hdr_idx);
+
+ while (1) {
+ cur_idx = htxn->s->txn->hdr_idx.v[old_idx].next;
+ if (!cur_idx)
+ break;
+ old_idx = cur_idx;
+
+ cur_hdr = &htxn->s->txn->hdr_idx.v[cur_idx];
+ cur_ptr = cur_next;
+ cur_next = cur_ptr + cur_hdr->len + cur_hdr->cr + 1;
+
+ /* Now we have one full header at cur_ptr of len cur_hdr->len,
+ * and the next header starts at cur_next. We'll check
+ * this header in the list as well as against the default
+ * rule.
+ */
+
+ /* look for ': *'. */
+ hn = cur_ptr;
+ for (p = cur_ptr; p < cur_ptr + cur_hdr->len && *p != ':'; p++);
+ if (p >= cur_ptr+cur_hdr->len)
+ continue;
+ hnl = p - hn;
+ p++;
+ while (p < cur_ptr+cur_hdr->len && ( *p == ' ' || *p == '\t' ))
+ p++;
+ if (p >= cur_ptr+cur_hdr->len)
+ continue;
+ hv = p;
+ hvl = cur_ptr+cur_hdr->len-p;
+
+ /* Lowercase the key. Don't check the size of trash, it have
+ * the size of one buffer and the input data contains in one
+ * buffer.
+ */
+ out = trash.str;
+ for (in=hn; in<hn+hnl; in++, out++)
+ *out = tolower(*in);
+ *out = '\0';
+
+ /* Check for existing entry:
+ * assume that the table is on the top of the stack, and
+ * push the key in the stack, the function lua_gettable()
+ * perform the lookup.
+ */
+ lua_pushlstring(L, trash.str, hnl);
+ lua_gettable(L, -2);
+ type = lua_type(L, -1);
+
+ switch (type) {
+ case LUA_TNIL:
+ /* Table not found, create it. */
+ lua_pop(L, 1); /* remove the nil value. */
+ lua_pushlstring(L, trash.str, hnl); /* push the header name as key. */
+ lua_newtable(L); /* create and push empty table. */
+ lua_pushlstring(L, hv, hvl); /* push header value. */
+ lua_rawseti(L, -2, 0); /* index header value (pop it). */
+ lua_rawset(L, -3); /* index new table with header name (pop the values). */
+ break;
+
+ case LUA_TTABLE:
+ /* Entry found: push the value in the table. */
+ len = lua_rawlen(L, -1);
+ lua_pushlstring(L, hv, hvl); /* push header value. */
+ lua_rawseti(L, -2, len+1); /* index header value (pop it). */
+ lua_pop(L, 1); /* remove the table (it is stored in the main table). */
+ break;
+
+ default:
+ /* Other cases are errors. */
+ hlua_pusherror(L, "internal error during the parsing of headers.");
+ WILL_LJMP(lua_error(L));
+ }
+ }
+
+ return 1;
+}
+
+__LJMP static int hlua_http_req_get_headers(lua_State *L)
+{
+ struct hlua_txn *htxn;
+
+ MAY_LJMP(check_args(L, 1, "req_get_headers"));
+ htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+ return hlua_http_get_headers(L, htxn, &htxn->s->txn->req);
+}
+
+__LJMP static int hlua_http_res_get_headers(lua_State *L)
+{
+ struct hlua_txn *htxn;
+
+ MAY_LJMP(check_args(L, 1, "res_get_headers"));
+ htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+ return hlua_http_get_headers(L, htxn, &htxn->s->txn->rsp);
+}
+
+/* This function replace full header, or just a value in
+ * the request or in the response. It is a wrapper fir the
+ * 4 following functions.
+ */
+__LJMP static inline int hlua_http_rep_hdr(lua_State *L, struct hlua_txn *htxn,
+ struct http_msg *msg, int action)
+{
+ size_t name_len;
+ const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+ const char *reg = MAY_LJMP(luaL_checkstring(L, 3));
+ const char *value = MAY_LJMP(luaL_checkstring(L, 4));
+ struct my_regex re;
+
+ if (!regex_comp(reg, &re, 1, 1, NULL))
+ WILL_LJMP(luaL_argerror(L, 3, "invalid regex"));
+
+ http_transform_header_str(htxn->s, msg, name, name_len, value, &re, action);
+ regex_free(&re);
+ return 0;
+}
+
+__LJMP static int hlua_http_req_rep_hdr(lua_State *L)
+{
+ struct hlua_txn *htxn;
+
+ MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
+ htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+ return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, ACT_HTTP_REPLACE_HDR));
+}
+
+__LJMP static int hlua_http_res_rep_hdr(lua_State *L)
+{
+ struct hlua_txn *htxn;
+
+ MAY_LJMP(check_args(L, 4, "res_rep_hdr"));
+ htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+ return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->rsp, ACT_HTTP_REPLACE_HDR));
+}
+
+__LJMP static int hlua_http_req_rep_val(lua_State *L)
+{
+ struct hlua_txn *htxn;
+
+ MAY_LJMP(check_args(L, 4, "req_rep_hdr"));
+ htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+ return MAY_LJMP(hlua_http_rep_hdr(L, htxn, &htxn->s->txn->req, ACT_HTTP_REPLACE_VAL));
+}
+
+__LJMP static int hlua_http_res_rep_val(lua_State *L)
+{
struct hlua_txn *htxn;
MAY_LJMP(check_args(L, 4, "res_rep_val"));
@@ -5081,6 +5735,272 @@
hlua_ctx_destroy(&ctx->ctx.hlua_apptcp.hlua);
}
+/* The function returns 1 if the initialisation is complete, 0 if
+ * an errors occurs and -1 if more data are required for initializing
+ * the applet.
+ */
+static int hlua_applet_http_init(struct appctx *ctx, struct proxy *px, struct stream *strm)
+{
+ struct stream_interface *si = ctx->owner;
+ struct channel *req = si_oc(si);
+ struct http_msg *msg;
+ struct http_txn *txn;
+ struct hlua *hlua = &ctx->ctx.hlua_apphttp.hlua;
+ char **arg;
+ struct hdr_ctx hdr;
+ struct task *task;
+ struct sample smp; /* just used for a valid call to smp_prefetch_http. */
+
+ /* Wait for a full HTTP request. */
+ if (!smp_prefetch_http(px, strm, 0, NULL, &smp, 0)) {
+ if (smp.flags & SMP_F_MAY_CHANGE)
+ return -1;
+ return 0;
+ }
+ txn = strm->txn;
+ msg = &txn->req;
+
+ HLUA_INIT(hlua);
+ ctx->ctx.hlua_apphttp.left_bytes = -1;
+ ctx->ctx.hlua_apphttp.flags = 0;
+
+ /* Create task used by signal to wakeup applets. */
+ task = task_new();
+ if (!task) {
+ SEND_ERR(px, "Lua applet http '%s': out of memory.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ return 0;
+ }
+ task->nice = 0;
+ task->context = ctx;
+ task->process = hlua_applet_wakeup;
+ ctx->ctx.hlua_apphttp.task = task;
+
+ /* In the execution wrappers linked with a stream, the
+ * Lua context can be not initialized. This behavior
+ * permits to save performances because a systematic
+ * Lua initialization cause 5% performances loss.
+ */
+ if (!hlua_ctx_init(hlua, task)) {
+ SEND_ERR(px, "Lua applet http '%s': can't initialize Lua context.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ return 0;
+ }
+
+ /* Set timeout according with the applet configuration. */
+ hlua->expire = tick_add_ifset(now_ms, ctx->applet->timeout);
+
+ /* The following Lua calls can fail. */
+ if (!SET_SAFE_LJMP(hlua->T)) {
+ SEND_ERR(px, "Lua applet http '%s': critical error.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ return 0;
+ }
+
+ /* Check stack available size. */
+ if (!lua_checkstack(hlua->T, 1)) {
+ SEND_ERR(px, "Lua applet http '%s': full stack.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ RESET_SAFE_LJMP(hlua->T);
+ return 0;
+ }
+
+ /* Restore the function in the stack. */
+ lua_rawgeti(hlua->T, LUA_REGISTRYINDEX, ctx->rule->arg.hlua_rule->fcn.function_ref);
+
+ /* Create and and push object stream in the stack. */
+ if (!hlua_applet_http_new(hlua->T, ctx)) {
+ SEND_ERR(px, "Lua applet http '%s': full stack.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ RESET_SAFE_LJMP(hlua->T);
+ return 0;
+ }
+ hlua->nargs = 1;
+
+ /* Look for a 100-continue expected. */
+ if (msg->flags & HTTP_MSGF_VER_11) {
+ hdr.idx = 0;
+ if (http_find_header2("Expect", 6, req->buf->p, &txn->hdr_idx, &hdr) &&
+ unlikely(hdr.vlen == 12 && strncasecmp(hdr.line+hdr.val, "100-continue", 12) == 0))
+ ctx->ctx.hlua_apphttp.flags |= APPLET_100C;
+ }
+
+ /* push keywords in the stack. */
+ for (arg = ctx->rule->arg.hlua_rule->args; arg && *arg; arg++) {
+ if (!lua_checkstack(hlua->T, 1)) {
+ SEND_ERR(px, "Lua applet http '%s': full stack.\n",
+ ctx->rule->arg.hlua_rule->fcn.name);
+ RESET_SAFE_LJMP(hlua->T);
+ return 0;
+ }
+ lua_pushstring(hlua->T, *arg);
+ hlua->nargs++;
+ }
+
+ RESET_SAFE_LJMP(hlua->T);
+
+ /* Wakeup the applet when data is ready for read. */
+ si_applet_cant_get(si);
+
+ return 1;
+}
+
+static void hlua_applet_http_fct(struct appctx *ctx)
+{
+ struct stream_interface *si = ctx->owner;
+ struct stream *strm = si_strm(si);
+ struct channel *res = si_ic(si);
+ struct channel *req = si_oc(si);
+ struct act_rule *rule = ctx->rule;
+ struct proxy *px = strm->be;
+ struct hlua *hlua = &ctx->ctx.hlua_apphttp.hlua;
+ char *blk1;
+ int len1;
+ char *blk2;
+ int len2;
+ int ret;
+
+ /* If the stream is disconnect or closed, ldo nothing. */
+ if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
+ return;
+
+ /* Set the currently running flag. */
+ if (!HLUA_IS_RUNNING(hlua) &&
+ !(ctx->ctx.hlua_apphttp.flags & APPLET_DONE)) {
+
+ /* enable the minimally required analyzers to handle keep-alive
+ * and compression on the HTTP response
+ */
+ req->analysers = (req->analysers & AN_REQ_HTTP_BODY) |
+ AN_REQ_HTTP_XFER_BODY | AN_REQ_HTTP_INNER;
+
+ /* Wait for full HTTP analysys. */
+ if (unlikely(strm->txn->req.msg_state < HTTP_MSG_BODY)) {
+ si_applet_cant_get(si);
+ return;
+ }
+
+ /* Store the max amount of bytes that we can read. */
+ ctx->ctx.hlua_apphttp.left_bytes = strm->txn->req.body_len;
+
+ /* We need to flush the request header. This left the body
+ * for the Lua.
+ */
+
+ /* Read the maximum amount of data avalaible. */
+ ret = bo_getblk_nc(si_oc(si), &blk1, &len1, &blk2, &len2);
+ if (ret == -1)
+ return;
+
+ /* No data available, ask for more data. */
+ if (ret == 1)
+ len2 = 0;
+ if (ret == 0)
+ len1 = 0;
+ if (len1 + len2 < strm->txn->req.eoh + 2) {
+ si_applet_cant_get(si);
+ return;
+ }
+
+ /* skip the requests bytes. */
+ bo_skip(si_oc(si), strm->txn->req.eoh + 2);
+ }
+
+ /* Executes The applet if it is not done. */
+ if (!(ctx->ctx.hlua_apphttp.flags & APPLET_DONE)) {
+
+ /* Execute the function. */
+ switch (hlua_ctx_resume(hlua, 1)) {
+ /* finished. */
+ case HLUA_E_OK:
+ ctx->ctx.hlua_apphttp.flags |= APPLET_DONE;
+ break;
+
+ /* yield. */
+ case HLUA_E_AGAIN:
+ return;
+
+ /* finished with error. */
+ case HLUA_E_ERRMSG:
+ /* Display log. */
+ SEND_ERR(px, "Lua applet http '%s': %s.\n",
+ rule->arg.hlua_rule->fcn.name, lua_tostring(hlua->T, -1));
+ lua_pop(hlua->T, 1);
+ goto error;
+
+ case HLUA_E_ERR:
+ /* Display log. */
+ SEND_ERR(px, "Lua applet http '%s' return an unknown error.\n",
+ rule->arg.hlua_rule->fcn.name);
+ goto error;
+
+ default:
+ goto error;
+ }
+ }
+
+ if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
+
+ /* We must send the final chunk. */
+ if (ctx->ctx.hlua_apphttp.flags & APPLET_CHUNKED &&
+ !(ctx->ctx.hlua_apphttp.flags & APPLET_LAST_CHK)) {
+
+ /* sent last chunk at once. */
+ ret = bi_putblk(res, "0\r\n\r\n", 5);
+
+ /* critical error. */
+ if (ret == -2 || ret == -3) {
+ SEND_ERR(px, "Lua applet http '%s'cannont send last chunk.\n",
+ rule->arg.hlua_rule->fcn.name);
+ goto error;
+ }
+
+ /* no enough space error. */
+ if (ret == -1) {
+ si_applet_cant_put(si);
+ return;
+ }
+
+ /* set the last chunk sent. */
+ ctx->ctx.hlua_apphttp.flags |= APPLET_LAST_CHK;
+ }
+
+ /* close the connection. */
+
+ /* status / log */
+ strm->txn->status = ctx->ctx.hlua_apphttp.status;
+ strm->logs.tv_request = now;
+
+ /* eat the whole request */
+ bo_skip(si_oc(si), si_ob(si)->o);
+ res->flags |= CF_READ_NULL;
+ si_shutr(si);
+
+ return;
+ }
+
+error:
+
+ /* If we are in HTTP mode, and we are not send any
+ * data, return a 500 server error in best effort:
+ * if there are no room avalaible in the buffer,
+ * just close the connection.
+ */
+ bi_putblk(res, error_500, strlen(error_500));
+ if (!(strm->flags & SF_ERR_MASK))
+ strm->flags |= SF_ERR_RESOURCE;
+ si_shutw(si);
+ si_shutr(si);
+ ctx->ctx.hlua_apphttp.flags |= APPLET_DONE;
+}
+
+static void hlua_applet_http_release(struct appctx *ctx)
+{
+ task_free(ctx->ctx.hlua_apphttp.task);
+ ctx->ctx.hlua_apphttp.task = NULL;
+ hlua_ctx_destroy(&ctx->ctx.hlua_apphttp.hlua);
+}
+
/* global {tcp|http}-request parser. Return ACT_RET_PRS_OK in
* succes case, else return ACT_RET_PRS_ERR.
*
@@ -5111,6 +6031,35 @@
return ACT_RET_PRS_OK;
}
+static enum act_parse_ret action_register_service_http(const char **args, int *cur_arg, struct proxy *px,
+ struct act_rule *rule, char **err)
+{
+ struct hlua_function *fcn = (struct hlua_function *)rule->kw->private;
+
+ /* Memory for the rule. */
+ rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule));
+ if (!rule->arg.hlua_rule) {
+ memprintf(err, "out of memory error");
+ return ACT_RET_PRS_ERR;
+ }
+
+ /* Reference the Lua function and store the reference. */
+ rule->arg.hlua_rule->fcn = *fcn;
+
+ /* TODO: later accept arguments. */
+ rule->arg.hlua_rule->args = NULL;
+
+ /* Add applet pointer in the rule. */
+ rule->applet.obj_type = OBJ_TYPE_APPLET;
+ rule->applet.name = fcn->name;
+ rule->applet.init = hlua_applet_http_init;
+ rule->applet.fct = hlua_applet_http_fct;
+ rule->applet.release = hlua_applet_http_release;
+ rule->applet.timeout = hlua_timeout_applet;
+
+ return ACT_RET_PRS_OK;
+}
+
/* This function is an LUA binding used for registering
* "sample-conv" functions. It expects a converter name used
* in the haproxy configuration file, and an LUA function.
@@ -5274,8 +6223,11 @@
if (strcmp(env, "tcp") == 0)
akl->kw[0].parse = action_register_service_tcp;
+ else if (strcmp(env, "http") == 0)
+ akl->kw[0].parse = action_register_service_http;
else
- WILL_LJMP(luaL_error(L, "lua service environment '%s' is unknown. 'tcp' is expected."));
+ WILL_LJMP(luaL_error(L, "lua service environment '%s' is unknown. "
+ "'tcp' or 'http' are expected."));
akl->kw[0].match_pfx = 0;
akl->kw[0].private = fcn;
@@ -5859,6 +6811,40 @@
/*
*
+ * Register class AppletHTTP
+ *
+ */
+
+ /* Create and fill the metatable. */
+ lua_newtable(gL.T);
+
+ /* Create the __tostring identifier */
+ lua_pushstring(gL.T, "__tostring");
+ lua_pushstring(gL.T, CLASS_APPLET_HTTP);
+ lua_pushcclosure(gL.T, hlua_dump_object, 1);
+ lua_rawset(gL.T, -3);
+
+ /* Create and fille the __index entry. */
+ lua_pushstring(gL.T, "__index");
+ lua_newtable(gL.T);
+
+ /* Register Lua functions. */
+ hlua_class_function(gL.T, "getline", hlua_applet_http_getline);
+ hlua_class_function(gL.T, "receive", hlua_applet_http_recv);
+ hlua_class_function(gL.T, "send", hlua_applet_http_send);
+ hlua_class_function(gL.T, "add_header", hlua_applet_http_addheader);
+ hlua_class_function(gL.T, "set_status", hlua_applet_http_status);
+ hlua_class_function(gL.T, "start_response", hlua_applet_http_start_response);
+
+ lua_settable(gL.T, -3);
+
+ /* Register previous table in the registry with reference and named entry. */
+ lua_pushvalue(gL.T, -1); /* Copy the -1 entry and push it on the stack. */
+ lua_setfield(gL.T, LUA_REGISTRYINDEX, CLASS_APPLET_HTTP); /* register class session. */
+ class_applet_http_ref = luaL_ref(gL.T, LUA_REGISTRYINDEX); /* reference class session. */
+
+ /*
+ *
* Register class TXN
*
*/