MEDIUM: filters/lua: Add HTTPMessage class to help HTTP filtering

This new class exposes methods to manipulate HTTP messages from a filter
written in lua. Like for the HTTP class, there is a bunch of methods to
manipulate the message headers. But there are also methods to manipulate the
message payload. This part is similar to what is available in the Channel
class. Thus the payload can be duplicated, erased, modified or
forwarded. For now, only DATA blocks can be retrieved and modified because
the current API is limited. No HTTPMessage method is able to yield. Those
manipulating the headers are always called on messages containing all the
headers, so there is no reason to yield. Those manipulating the payload are
called from the http_payload filters callback function where yielding is
forbidden.

When an HTTPMessage object is instantiated, the underlying Channel object
can be retrieved via the ".channel" field.

For now this class is not used because the HTTP filtering is not supported
yet. It will be the purpose of another commit.

There is no documentation for now.
diff --git a/src/hlua.c b/src/hlua.c
index 25a3e51..c1a578e 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -259,6 +259,7 @@
 static int class_fetches_ref;
 static int class_converters_ref;
 static int class_http_ref;
+static int class_http_msg_ref;
 static int class_map_ref;
 static int class_applet_tcp_ref;
 static int class_applet_http_ref;
@@ -5502,6 +5503,39 @@
 	return 1;
 }
 
+/* This function creates and returns an array containing the status-line
+ * elements. This function does not fails.
+ */
+__LJMP static int hlua_http_get_stline(lua_State *L, struct htx_sl *sl)
+{
+	/* Create the table. */
+	lua_newtable(L);
+
+	if (sl->flags & HTX_SL_F_IS_RESP) {
+		lua_pushstring(L, "version");
+		lua_pushlstring(L, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl));
+		lua_settable(L, -3);
+		lua_pushstring(L, "code");
+		lua_pushlstring(L, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
+		lua_settable(L, -3);
+		lua_pushstring(L, "reason");
+		lua_pushlstring(L, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl));
+		lua_settable(L, -3);
+	}
+	else {
+		lua_pushstring(L, "method");
+		lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl));
+		lua_settable(L, -3);
+		lua_pushstring(L, "uri");
+		lua_pushlstring(L, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl));
+		lua_settable(L, -3);
+		lua_pushstring(L, "version");
+		lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl));
+		lua_settable(L, -3);
+	}
+	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.
@@ -5725,141 +5759,1116 @@
 	return 0;
 }
 
+__LJMP static int hlua_http_req_add_hdr(lua_State *L)
+{
+	struct hlua_txn *htxn;
+
+	MAY_LJMP(check_args(L, 3, "req_add_hdr"));
+	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_add_hdr(L, &htxn->s->txn->req);
+}
+
+__LJMP static int hlua_http_res_add_hdr(lua_State *L)
+{
+	struct hlua_txn *htxn;
+
+	MAY_LJMP(check_args(L, 3, "res_add_hdr"));
+	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_add_hdr(L, &htxn->s->txn->rsp);
+}
+
+static int hlua_http_req_set_hdr(lua_State *L)
+{
+	struct hlua_txn *htxn;
+
+	MAY_LJMP(check_args(L, 3, "req_set_hdr"));
+	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	hlua_http_del_hdr(L, &htxn->s->txn->req);
+	return hlua_http_add_hdr(L, &htxn->s->txn->req);
+}
+
+static int hlua_http_res_set_hdr(lua_State *L)
+{
+	struct hlua_txn *htxn;
+
+	MAY_LJMP(check_args(L, 3, "res_set_hdr"));
+	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+
+	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	hlua_http_del_hdr(L, &htxn->s->txn->rsp);
+	return hlua_http_add_hdr(L, &htxn->s->txn->rsp);
+}
+
+/* This function set the method. */
+static int hlua_http_req_set_meth(lua_State *L)
+{
+	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	size_t name_len;
+	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	lua_pushboolean(L, http_req_replace_stline(0, name, name_len, htxn->p, htxn->s) != -1);
+	return 1;
+}
+
+/* This function set the method. */
+static int hlua_http_req_set_path(lua_State *L)
+{
+	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	size_t name_len;
+	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	lua_pushboolean(L, http_req_replace_stline(1, name, name_len, htxn->p, htxn->s) != -1);
+	return 1;
+}
+
+/* This function set the query-string. */
+static int hlua_http_req_set_query(lua_State *L)
+{
+	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	size_t name_len;
+	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	/* Check length. */
+	if (name_len > trash.size - 1) {
+		lua_pushboolean(L, 0);
+		return 1;
+	}
+
+	/* Add the mark question as prefix. */
+	chunk_reset(&trash);
+	trash.area[trash.data++] = '?';
+	memcpy(trash.area + trash.data, name, name_len);
+	trash.data += name_len;
+
+	lua_pushboolean(L,
+			http_req_replace_stline(2, trash.area, trash.data, htxn->p, htxn->s) != -1);
+	return 1;
+}
+
+/* This function set the uri. */
+static int hlua_http_req_set_uri(lua_State *L)
+{
+	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	size_t name_len;
+	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	lua_pushboolean(L, http_req_replace_stline(3, name, name_len, htxn->p, htxn->s) != -1);
+	return 1;
+}
+
+/* This function set the response code & optionally reason. */
+static int hlua_http_res_set_status(lua_State *L)
+{
+	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	unsigned int code = MAY_LJMP(luaL_checkinteger(L, 2));
+	const char *str = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL));
+	const struct ist reason = ist2(str, (str ? strlen(str) : 0));
+
+	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
+		WILL_LJMP(lua_error(L));
+
+	http_res_set_status(code, reason, htxn->s);
+	return 0;
+}
+
+/*
+ *
+ *
+ * Class HTTPMessage
+ *
+ *
+ */
+
+/* Returns a struct http_msg if the stack entry "ud" is a class HTTPMessage,
+ * otherwise it throws an error.
+ */
+__LJMP static struct http_msg *hlua_checkhttpmsg(lua_State *L, int ud)
+{
+	return MAY_LJMP(hlua_checkudata(L, ud, class_http_msg_ref));
+}
+
+/* Creates and pushes on the stack a HTTP object according with a current TXN.
+ */
+static __maybe_unused int hlua_http_msg_new(lua_State *L, struct http_msg *msg)
+{
+	/* Check stack size. */
+	if (!lua_checkstack(L, 3))
+		return 0;
+
+	lua_newtable(L);
+	lua_pushlightuserdata(L, msg);
+	lua_rawseti(L, -2, 0);
+
+	/* Create the "channel" field that contains the request channel object. */
+	lua_pushstring(L, "channel");
+	if (!hlua_channel_new(L, msg->chn))
+		return 0;
+	lua_rawset(L, -3);
+
+	/* Pop a class stream metatable and affect it to the table. */
+	lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_msg_ref);
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+/* Helper function returning a filter attached to the HTTP message at the
+ * position <ud> in the stack, filling the current offset and length of the
+ * filter. If no filter is attached, NULL is returned and <offet> and <len> are
+ * filled with output and input length respectively.
+ */
+static struct filter *hlua_http_msg_filter(lua_State *L, int ud, struct http_msg *msg, size_t *offset, size_t *len)
+{
+	struct channel *chn = msg->chn;
+	struct htx *htx = htxbuf(&chn->buf);
+	struct filter *filter = NULL;
+
+	*offset = co_data(msg->chn);
+	*len    = htx->data - co_data(msg->chn);
+
+	if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) {
+		filter  = lua_touserdata (L, -1);
+		if (msg->msg_state >= HTTP_MSG_DATA) {
+			struct hlua_flt_ctx *flt_ctx = filter->ctx;
+
+			*offset  = flt_ctx->cur_off[CHN_IDX(chn)];
+			*len     = flt_ctx->cur_len[CHN_IDX(chn)];
+		}
+	}
+
+	lua_pop(L, 1);
+	return filter;
+}
+
+/* Returns true if the channel attached to the HTTP message is the response
+ * channel.
+ */
+__LJMP static int hlua_http_msg_is_resp(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 1, "is_resp"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	lua_pushboolean(L, !!(msg->chn->flags & CF_ISRESP));
+	return 1;
+}
+
+/* Returns an array containing the elements status-line of the HTTP message. It relies
+ * on hlua_http_get_stline().
+ */
+__LJMP static int hlua_http_msg_get_stline(lua_State *L)
+{
+	struct http_msg *msg;
+	struct htx *htx;
+	struct htx_sl *sl;
+
+	MAY_LJMP(check_args(L, 1, "get_stline"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	htx = htxbuf(&msg->chn->buf);
+	sl = http_get_stline(htx);
+	if (!sl)
+		return 0;
+	return hlua_http_get_stline(L, sl);
+}
+
+/* Returns an array containing all headers of the HTTP message. it relies on
+ * hlua_http_get_headers().
+ */
+__LJMP static int hlua_http_msg_get_headers(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 1, "get_headers"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_get_headers(L, msg);
+}
+
+/* Deletes all occurrences of an header in the HTTP message matching on its
+ * name. It relies on hlua_http_del_hdr().
+ */
+__LJMP static int hlua_http_msg_del_hdr(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 2, "del_header"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_del_hdr(L, msg);
+}
+
+/* Matches the full value line of all occurences of an header in the HTTP
+ * message given its name against a regex and replaces it if it matches. It
+ * relies on hlua_http_rep_hdr().
+ */
+__LJMP static int hlua_http_msg_rep_hdr(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 4, "rep_header"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_rep_hdr(L, msg, 1);
+}
+
+/* Matches all comma-separated values of all occurences of an header in the HTTP
+ * message given its name against a regex and replaces it if it matches. It
+ * relies on hlua_http_rep_hdr().
+ */
+__LJMP static int hlua_http_msg_rep_val(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 4, "rep_value"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_rep_hdr(L, msg, 0);
+}
+
+/* Add an header in the HTTP message. It relies on hlua_http_add_hdr() */
+__LJMP static int hlua_http_msg_add_hdr(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 3, "add_header"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	return hlua_http_add_hdr(L, msg);
+}
+
+/* Add an header in the HTTP message removing existing headers with the same
+ * name. It relies on hlua_http_del_hdr() and hlua_http_add_hdr().
+ */
+__LJMP static int hlua_http_msg_set_hdr(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 3, "set_header"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	hlua_http_del_hdr(L, msg);
+	return hlua_http_add_hdr(L, msg);
+}
+
+/* Rewrites the request method. It relies on http_req_replace_stline(). */
+__LJMP static int hlua_http_msg_set_meth(lua_State *L)
+{
+	struct stream *s;
+	struct http_msg *msg;
+	const char *name;
+	size_t name_len;
+
+	MAY_LJMP(check_args(L, 2, "set_method"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	s = chn_strm(msg->chn);
+	lua_pushboolean(L, http_req_replace_stline(0, name, name_len, s->be, s) != -1);
+	return 1;
+}
+
+/* Rewrites the request path. It relies on http_req_replace_stline(). */
+__LJMP static int hlua_http_msg_set_path(lua_State *L)
+{
+	struct stream *s;
+	struct http_msg *msg;
+	const char *name;
+	size_t name_len;
+
+	MAY_LJMP(check_args(L, 2, "set_path"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	s = chn_strm(msg->chn);
+	lua_pushboolean(L, http_req_replace_stline(1, name, name_len, s->be, s) != -1);
+	return 1;
+}
+
+/* Rewrites the request query-string. It relies on http_req_replace_stline(). */
+__LJMP static int hlua_http_msg_set_query(lua_State *L)
+{
+	struct stream *s;
+	struct http_msg *msg;
+	const char *name;
+	size_t name_len;
+
+	MAY_LJMP(check_args(L, 2, "set_query"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	/* Check length. */
+	if (name_len > trash.size - 1) {
+		lua_pushboolean(L, 0);
+		return 1;
+	}
+
+	/* Add the mark question as prefix. */
+	chunk_reset(&trash);
+	trash.area[trash.data++] = '?';
+	memcpy(trash.area + trash.data, name, name_len);
+	trash.data += name_len;
+
+	s = chn_strm(msg->chn);
+	lua_pushboolean(L, http_req_replace_stline(2, trash.area, trash.data, s->be, s) != -1);
+	return 1;
+}
+
+/* Rewrites the request URI. It relies on http_req_replace_stline(). */
+__LJMP static int hlua_http_msg_set_uri(lua_State *L)
+{
+	struct stream *s;
+	struct http_msg *msg;
+	const char *name;
+	size_t name_len;
+
+	MAY_LJMP(check_args(L, 2, "set_uri"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+
+	if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	s = chn_strm(msg->chn);
+	lua_pushboolean(L, http_req_replace_stline(3, name, name_len, s->be, s) != -1);
+	return 1;
+}
+
+/* Rewrites the response status code. It relies on http_res_set_status(). */
+__LJMP static int hlua_http_msg_set_status(lua_State *L)
+{
+	struct http_msg *msg;
+	unsigned int code;
+	const char *reason;
+	size_t reason_len;
+
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	code = MAY_LJMP(luaL_checkinteger(L, 2));
+	reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, &reason_len));
+
+	if (!(msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY)
+		WILL_LJMP(lua_error(L));
+
+	lua_pushboolean(L, http_res_set_status(code, ist2(reason, reason_len), chn_strm(msg->chn)) != -1);
+	return 1;
+}
+
+/* Returns true if the HTTP message is full. */
+__LJMP static int hlua_http_msg_is_full(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 1, "is_full"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	lua_pushboolean(L, channel_full(msg->chn, 0));
+	return 1;
+}
+
+/* Returns true if the HTTP message may still receive data. */
+__LJMP static int hlua_http_msg_may_recv(lua_State *L)
+{
+	struct http_msg *msg;
+	struct htx *htx;
+
+	MAY_LJMP(check_args(L, 1, "may_recv"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	htx = htxbuf(&msg->chn->buf);
+	lua_pushboolean(L, (htx_expect_more(htx) && !channel_input_closed(msg->chn) && channel_may_recv(msg->chn)));
+	return 1;
+}
+
+/* Returns true if the HTTP message EOM was received */
+__LJMP static int hlua_http_msg_is_eom(lua_State *L)
+{
+	struct http_msg *msg;
+	struct htx *htx;
+
+	MAY_LJMP(check_args(L, 1, "may_recv"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	htx = htxbuf(&msg->chn->buf);
+	lua_pushboolean(L, !htx_expect_more(htx));
+	return 1;
+}
+
+/* Returns the number of bytes available in the input side of the HTTP
+ * message. This function never fails.
+ */
+__LJMP static int hlua_http_msg_get_in_len(lua_State *L)
+{
+	struct http_msg *msg;
+	struct htx *htx;
+
+	MAY_LJMP(check_args(L, 1, "input"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	htx = htxbuf(&msg->chn->buf);
+	lua_pushinteger(L, htx->data - co_data(msg->chn));
+	return 1;
+}
+
+/* Returns the number of bytes available in the output side of the HTTP
+ * message. This function never fails.
+ */
+__LJMP static int hlua_http_msg_get_out_len(lua_State *L)
+{
+	struct http_msg *msg;
+
+	MAY_LJMP(check_args(L, 1, "output"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	lua_pushinteger(L, co_data(msg->chn));
+	return 1;
+}
+
+/* Copies at most <len> bytes of DATA blocks from the HTTP message <msg>
+ * starting at the offset <offset> and put it in a string LUA variables. It
+ * returns the length of the builded string. It stops on the first non-DATA HTX
+ * block. This function is called during the payload filtering, so the headers
+ * are already scheduled for output (from the filter point of view).
+ */
+static int _hlua_http_msg_dup(struct http_msg *msg, lua_State *L, size_t offset, size_t len)
+{
+	struct htx *htx = htxbuf(&msg->chn->buf);
+	struct htx_blk *blk;
+	struct htx_ret htxret;
+	luaL_Buffer b;
+	int ret = 0;
+
+	luaL_buffinit(L, &b);
+	htxret = htx_find_offset(htx, offset);
+	for (blk = htxret.blk, offset = htxret.ret; blk && len; blk = htx_get_next_blk(htx, blk)) {
+		enum htx_blk_type type = htx_get_blk_type(blk);
+		struct ist v;
+
+		switch (type) {
+			case HTX_BLK_UNUSED:
+				break;
+
+			case HTX_BLK_DATA:
+				v = htx_get_blk_value(htx, blk);
+				v.ptr += offset;
+				v.len -= offset;
+				if (v.len > len)
+					v.len = len;
+
+				luaL_addlstring(&b, v.ptr, v.len);
+				ret += v.len;
+				break;
+
+			default:
+				if (!ret) {
+					/* Remove the empty string and push nil on the stack */
+					lua_pop(L, 1);
+					lua_pushnil(L);
+				}
+				goto end;
+		}
+		offset = 0;
+	}
+
+	luaL_pushresult(&b);
+
+end:
+	return ret;
+}
+
+/* Copies the string <str> to the HTTP message <msg> at the offset
+ * <offset>. This function returns -1 if data cannot be copied. Otherwise, it
+ * returns the amount of data written. This function is responsibile to update
+ * the filter context.
+ */
+static int _hlua_http_msg_insert(struct http_msg *msg, struct filter *filter, struct ist str, size_t offset)
+{
+	struct htx *htx = htx_from_buf(&msg->chn->buf);
+	struct htx_ret htxret;
+	int /*max, */ret = 0;
+
+	/* Nothing to do, just return */
+	if (unlikely(istlen(str) == 0))
+		goto end;
+
+	if (istlen(str) > htx_free_data_space(htx)) {
+		ret = -1;
+		goto end;
+	}
+
+	htxret = htx_find_offset(htx, offset);
+	if (!htxret.blk || htx_get_blk_type(htxret.blk) != HTX_BLK_DATA) {
+		if (!htx_add_last_data(htx, str))
+			goto end;
+	}
+	else {
+		struct ist v = htx_get_blk_value(htx, htxret.blk);
+		v.ptr += htxret.ret;
+		v.len  = 0;
+		if (!htx_replace_blk_value(htx, htxret.blk, v, str))
+			goto end;
+	}
+	ret = str.len;
+	if (ret) {
+		struct hlua_flt_ctx *flt_ctx = filter->ctx;
+		flt_update_offsets(filter, msg->chn, ret);
+		flt_ctx->cur_len[CHN_IDX(msg->chn)] += ret;
+	}
+
+  end:
+	htx_to_buf(htx, &msg->chn->buf);
+	return ret;
+}
+
+/* Helper function removing at most <len> bytes of DATA blocks at the absolute
+ * position <offset>. It stops on the first non-DATA HTX block. This function is
+ * called during the payload filtering, so the headers are already scheduled for
+ * output (from the filter point of view). This function is responsibile to
+ * update the filter context.
+ */
+static void _hlua_http_msg_delete(struct http_msg *msg, struct filter *filter, size_t offset, size_t len)
+{
+	struct hlua_flt_ctx *flt_ctx = filter->ctx;
+	struct htx *htx = htx_from_buf(&msg->chn->buf);
+	struct htx_blk *blk;
+	struct htx_ret htxret;
+	size_t ret = 0;
+
+	/* Be sure <len> is always the amount of DATA to remove */
+	if (htx->data == offset+len && htx_get_tail_type(htx) == HTX_BLK_DATA) {
+		htx_truncate(htx, offset);
+		ret = len;
+		goto end;
+	}
+
+	htxret = htx_find_offset(htx, offset);
+	blk = htxret.blk;
+	if (htxret.ret) {
+		struct ist v;
+
+		if (htx_get_blk_type(blk) != HTX_BLK_DATA)
+			goto end;
+		v = htx_get_blk_value(htx, blk);
+		v.ptr += htxret.ret;
+		if (v.len > len)
+			v.len  = len;
+		blk = htx_replace_blk_value(htx, blk, v, ist2(NULL, 0));
+		len -= v.len;
+		ret += v.len;
+	}
+
+
+	while (blk && len) {
+		enum htx_blk_type type = htx_get_blk_type(blk);
+		uint32_t sz = htx_get_blksz(blk);
+
+		switch (type) {
+			case HTX_BLK_UNUSED:
+				break;
+
+			case HTX_BLK_DATA:
+				if (len < sz) {
+					htx_cut_data_blk(htx, blk, len);
+					ret += len;
+					goto end;
+				}
+				break;
+
+			default:
+				goto end;
+		}
+
+		/* Remove oll the data block */
+		len -= sz;
+		ret += sz;
+		blk = htx_remove_blk(htx, blk);
+	}
+
+end:
+	flt_update_offsets(filter, msg->chn, -ret);
+	flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret;
+	/* WARNING: we don't call htx_to_buf() on purpose, because we don't want
+	 *          to loose the EOM flag if the message is empty.
+	 */
+}
+
+/* Copies input data found in an HTTP message. Unlike the channel function used
+ * to duplicate raw data, this one can only be called inside a filter, from
+ * http_payload callback. So it cannot yield. An exception is returned if it is
+ * called from another callback. If nothing was copied, a nil value is pushed on
+ * the stack.
+ */
+__LJMP static int hlua_http_msg_get_body(lua_State *L)
+{
+	struct http_msg *msg;
+	struct filter *filter;
+	size_t output, input;
+	int offset, len;
+
+	if (lua_gettop(L) < 1 || lua_gettop(L) > 3)
+		WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state < HTTP_MSG_DATA)
+		WILL_LJMP(lua_error(L));
+
+	filter = hlua_http_msg_filter(L, 1, msg, &output, &input);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
+
+	if (!ci_data(msg->chn) && channel_input_closed(msg->chn)) {
+		lua_pushnil(L);
+		return 1;
+	}
+
+	offset = output;
+	if (lua_gettop(L) > 1) {
+		offset = MAY_LJMP(luaL_checkinteger(L, 2));
+		if (offset < 0)
+			offset = MAX(0, input + offset);
+		offset += output;
+		if (offset < output || offset > input + output) {
+			lua_pushfstring(L, "offset out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
+	len = output + input - offset;
+	if (lua_gettop(L) == 3) {
+		len = MAY_LJMP(luaL_checkinteger(L, 3));
+		if (!len)
+			goto dup;
+		if (len == -1)
+			len = global.tune.bufsize;
+		if (len < 0) {
+			lua_pushfstring(L, "length out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
+
+  dup:
+	_hlua_http_msg_dup(msg, L, offset, len);
+	return 1;
+}
+
+/* Appends a string to the HTTP message, after all existing DATA blocks but
+ * before the trailers, if any. It returns the amount of data written or -1 if
+ * nothing was copied. Unlike the channel function used to append data, this one
+ * can only be called inside a filter, from http_payload callback. So it cannot
+ * yield. An exception is returned if it is called from another callback.
+ */
+__LJMP static int hlua_http_msg_append(lua_State *L)
+{
+	struct http_msg *msg;
+	struct filter *filter;
+	const char *str;
+	size_t offset, len, sz;
+	int ret;
+
+	MAY_LJMP(check_args(L, 2, "append"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state < HTTP_MSG_DATA)
+		WILL_LJMP(lua_error(L));
+
+	str = MAY_LJMP(luaL_checklstring(L, 2, &sz));
+	filter = hlua_http_msg_filter(L, 1, msg, &offset, &len);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
+
+	ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset+len);
+	lua_pushinteger(L, ret);
+	return 1;
+}
+
+/* Prepends a string to the HTTP message, before all existing DATA blocks. It
+ * returns the amount of data written or -1 if nothing was copied. Unlike the
+ * channel function used to prepend data, this one can only be called inside a
+ * filter, from http_payload callback. So it cannot yield. An exception is
+ * returned if it is called from another callback.
+ */
+__LJMP static int hlua_http_msg_prepend(lua_State *L)
+{
+	struct http_msg *msg;
+	struct filter *filter;
+	const char *str;
+	size_t offset, len, sz;
+	int ret;
+
-__LJMP static int hlua_http_req_add_hdr(lua_State *L)
-{
-	struct hlua_txn *htxn;
+	MAY_LJMP(check_args(L, 2, "prepend"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
 
-	MAY_LJMP(check_args(L, 3, "req_add_hdr"));
-	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	if (msg->msg_state < HTTP_MSG_DATA)
+		WILL_LJMP(lua_error(L));
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+	str = MAY_LJMP(luaL_checklstring(L, 2, &sz));
+	filter = hlua_http_msg_filter(L, 1, msg, &offset, &len);
+	if (!filter || !hlua_filter_from_payload(filter))
 		WILL_LJMP(lua_error(L));
 
-	return hlua_http_add_hdr(L, &htxn->s->txn->req);
+	ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset);
+	lua_pushinteger(L, ret);
+	return 1;
 }
 
-__LJMP static int hlua_http_res_add_hdr(lua_State *L)
+/* Inserts a string to the HTTP message at a given offset. By default the string
+ * is appended at the end of DATA blocks. It returns the amount of data written
+ * or -1 if nothing was copied. Unlike the channel function used to insert data,
+ * this one can only be called inside a filter, from http_payload callback. So
+ * it cannot yield. An exception is returned if it is called from another
+ * callback.
+ */
+__LJMP static int hlua_http_msg_insert_data(lua_State *L)
 {
-	struct hlua_txn *htxn;
+	struct http_msg *msg;
+	struct filter *filter;
+	const char *str;
+	size_t input, output, sz;
+	int offset;
+	int ret;
 
-	MAY_LJMP(check_args(L, 3, "res_add_hdr"));
-	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	if (lua_gettop(L) < 2 || lua_gettop(L) > 3)
+		WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments"));
+	MAY_LJMP(check_args(L, 2, "insert"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
 
-	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
+	if (msg->msg_state < HTTP_MSG_DATA)
 		WILL_LJMP(lua_error(L));
 
-	return hlua_http_add_hdr(L, &htxn->s->txn->rsp);
-}
-
-static int hlua_http_req_set_hdr(lua_State *L)
-{
-	struct hlua_txn *htxn;
+	str = MAY_LJMP(luaL_checklstring(L, 2, &sz));
+	filter = hlua_http_msg_filter(L, 1, msg, &input, &output);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
 
-	MAY_LJMP(check_args(L, 3, "req_set_hdr"));
-	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	offset = input + output;
+	if (lua_gettop(L) > 2) {
+		offset = MAY_LJMP(luaL_checkinteger(L, 3));
+		if (offset < 0)
+			offset = MAX(0, input + offset);
+		offset += output;
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
-		WILL_LJMP(lua_error(L));
+		if (offset < output || offset > output + input) {
+			lua_pushfstring(L, "offset out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
 
-	hlua_http_del_hdr(L, &htxn->s->txn->req);
-	return hlua_http_add_hdr(L, &htxn->s->txn->req);
+	ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset);
+	lua_pushinteger(L, ret);
+	return 1;
 }
 
-static int hlua_http_res_set_hdr(lua_State *L)
+/* Removes a given amount of data from the HTTP message at a given offset. By
+ * default all DATA blocks are removed. It returns the amount of data
+ * removed. Unlike the channel function used to remove data, this one can only
+ * be called inside a filter, from http_payload callback. So it cannot yield. An
+ * exception is returned if it is called from another callback.
+ */
+__LJMP static int hlua_http_msg_del_data(lua_State *L)
 {
-	struct hlua_txn *htxn;
+	struct http_msg *msg;
+	struct filter *filter;
+	size_t input, output;
+	int offset, len;
 
-	MAY_LJMP(check_args(L, 3, "res_set_hdr"));
-	htxn = MAY_LJMP(hlua_checkhttp(L, 1));
+	if (lua_gettop(L) < 1 || lua_gettop(L) > 3)
+		WILL_LJMP(luaL_error(L, "'insert' expects at most 2 arguments"));
+	MAY_LJMP(check_args(L, 2, "insert"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
 
-	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
+	if (msg->msg_state < HTTP_MSG_DATA)
 		WILL_LJMP(lua_error(L));
 
-	hlua_http_del_hdr(L, &htxn->s->txn->rsp);
-	return hlua_http_add_hdr(L, &htxn->s->txn->rsp);
-}
+	filter = hlua_http_msg_filter(L, 1, msg, &input, &output);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
 
-/* This function set the method. */
-static int hlua_http_req_set_meth(lua_State *L)
-{
-	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-	size_t name_len;
-	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+	offset = input + output;
+	if (lua_gettop(L) > 2) {
+		offset = MAY_LJMP(luaL_checkinteger(L, 3));
+		if (offset < 0)
+			offset = MAX(0, input + offset);
+		offset += output;
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
-		WILL_LJMP(lua_error(L));
+		if (offset < output || offset > output + input) {
+			lua_pushfstring(L, "offset out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
 
-	lua_pushboolean(L, http_req_replace_stline(0, name, name_len, htxn->p, htxn->s) != -1);
+	len = output + input - offset;
+	if (lua_gettop(L) == 4) {
+		len = MAY_LJMP(luaL_checkinteger(L, 4));
+		if (!len)
+			goto end;
+		if (len == -1)
+			len = output + input - offset;
+		if (len < 0 || offset + len > output + input) {
+			lua_pushfstring(L, "length out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
+
+	_hlua_http_msg_delete(msg, filter, offset, len);
+
+  end:
+	lua_pushinteger(L, len);
 	return 1;
 }
 
-/* This function set the method. */
-static int hlua_http_req_set_path(lua_State *L)
+/* Replaces a given amount of data at the given offet by a string. By default,
+ * all remaining data are removed, accordingly to the filter context. It returns
+ * the amount of data written or -1 if nothing was copied. Unlike the channel
+ * function used to replace data, this one can only be called inside a filter,
+ * from http_payload callback. So it cannot yield. An exception is returned if
+ * it is called from another callback.
+ */
+__LJMP static int hlua_http_msg_set_data(lua_State *L)
 {
-	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-	size_t name_len;
-	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+	struct http_msg *msg;
+	struct filter *filter;
+	struct htx *htx;
+	const char *str;
+	size_t input, output, sz;
+	int offset, len;
+	int ret;
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+	if (lua_gettop(L) < 2 || lua_gettop(L) > 4)
+		WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state < HTTP_MSG_DATA)
 		WILL_LJMP(lua_error(L));
 
-	lua_pushboolean(L, http_req_replace_stline(1, name, name_len, htxn->p, htxn->s) != -1);
+	str = MAY_LJMP(luaL_checklstring(L, 2, &sz));
+	filter = hlua_http_msg_filter(L, 1, msg, &output, &input);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
+
+	offset = output;
+	if (lua_gettop(L) > 2) {
+		offset = MAY_LJMP(luaL_checkinteger(L, 3));
+		if (offset < 0)
+			offset = MAX(0, input + offset);
+		offset += output;
+		if (offset < output || offset > input + output) {
+			lua_pushfstring(L, "offset out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
+
+	len = output + input - offset;
+	if (lua_gettop(L) == 4) {
+		len = MAY_LJMP(luaL_checkinteger(L, 4));
+		if (!len)
+			goto set;
+		if (len == -1)
+			len = output + input - offset;
+		if (len < 0 || offset + len > output + input) {
+			lua_pushfstring(L, "length out of range.");
+			WILL_LJMP(lua_error(L));
+		}
+	}
+
+  set:
+	/* Be sure we can copied the string once input data will be removed. */
+	htx = htx_from_buf(&msg->chn->buf);
+	if (sz > htx_free_data_space(htx) + len)
+		lua_pushinteger(L, -1);
+	else {
+		_hlua_http_msg_delete(msg, filter, offset, len);
+		ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset);
+		lua_pushinteger(L, ret);
+	}
 	return 1;
 }
 
-/* This function set the query-string. */
-static int hlua_http_req_set_query(lua_State *L)
+/* Prepends data into an HTTP message and forward it, from the filter point of
+ * view. It returns the amount of data written or -1 if nothing was sent. Unlike
+ * the channel function used to send data, this one can only be called inside a
+ * filter, from http_payload callback. So it cannot yield. An exception is
+ * returned if it is called from another callback.
+ */
+__LJMP static int hlua_http_msg_send(lua_State *L)
 {
-	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-	size_t name_len;
-	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+	struct http_msg *msg;
+	struct filter *filter;
+	struct htx *htx;
+	const char *str;
+	size_t offset, len, sz;
+	int ret;
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+	MAY_LJMP(check_args(L, 2, "send"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state < HTTP_MSG_DATA)
 		WILL_LJMP(lua_error(L));
 
-	/* Check length. */
-	if (name_len > trash.size - 1) {
-		lua_pushboolean(L, 0);
+	str = MAY_LJMP(luaL_checklstring(L, 2, &sz));
+	filter = hlua_http_msg_filter(L, 1, msg, &offset, &len);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
+
+	/* Return an error if the channel's output is closed */
+	if (unlikely(channel_output_closed(msg->chn))) {
+		lua_pushinteger(L, -1);
 		return 1;
 	}
 
-	/* Add the mark question as prefix. */
-	chunk_reset(&trash);
-	trash.area[trash.data++] = '?';
-	memcpy(trash.area + trash.data, name, name_len);
-	trash.data += name_len;
+	htx = htx_from_buf(&msg->chn->buf);
+	if (sz > htx_free_data_space(htx)) {
+		lua_pushinteger(L, -1);
+		return 1;
+	}
 
-	lua_pushboolean(L,
-			http_req_replace_stline(2, trash.area, trash.data, htxn->p, htxn->s) != -1);
+	ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset);
+	if (ret > 0) {
+		struct hlua_flt_ctx *flt_ctx = filter->ctx;
+
+		FLT_OFF(filter, msg->chn) += ret;
+		flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret;
+		flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret;
+	}
+
+	lua_pushinteger(L, ret);
 	return 1;
 }
 
-/* This function set the uri. */
-static int hlua_http_req_set_uri(lua_State *L)
+/* Forwards a given amount of bytes. It return -1 if the channel's output is
+ * closed. Otherwise, it returns the number of bytes forwarded. Unlike the
+ * channel function used to forward data, this one can only be called inside a
+ * filter, from http_payload callback. So it cannot yield. An exception is
+ * returned if it is called from another callback. All other functions deal with
+ * DATA block, this one not.
+*/
+__LJMP static int hlua_http_msg_forward(lua_State *L)
 {
-	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-	size_t name_len;
-	const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len));
+	struct http_msg *msg;
+	struct filter *filter;
+	size_t offset, len;
+	int fwd, ret = 0;
 
-	if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s))
+	MAY_LJMP(check_args(L, 2, "forward"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+
+	if (msg->msg_state < HTTP_MSG_DATA)
 		WILL_LJMP(lua_error(L));
 
-	lua_pushboolean(L, http_req_replace_stline(3, name, name_len, htxn->p, htxn->s) != -1);
+	fwd = MAY_LJMP(luaL_checkinteger(L, 2));
+	filter = hlua_http_msg_filter(L, 1, msg, &offset, &len);
+	if (!filter || !hlua_filter_from_payload(filter))
+		WILL_LJMP(lua_error(L));
+
+	/* Nothing to do, just return */
+	if (!fwd)
+		goto end;
+
+	/* Return an error if the channel's output is closed */
+	if (unlikely(channel_output_closed(msg->chn))) {
+		ret = -1;
+		goto end;
+	}
+
+	ret = fwd;
+	if (ret > len)
+		ret = len;
+
+	if (ret) {
+		struct hlua_flt_ctx *flt_ctx = filter->ctx;
+
+		FLT_OFF(filter, msg->chn) += ret;
+		flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret;
+		flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret;
+	}
+
+  end:
+	lua_pushinteger(L, ret);
 	return 1;
 }
 
-/* This function set the response code & optionally reason. */
-static int hlua_http_res_set_status(lua_State *L)
+/* Set EOM flag on the HTX message.
+ *
+ * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't
+ *       really know how to do without this feature.
+ */
+__LJMP static int hlua_http_msg_set_eom(lua_State *L)
 {
-	struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1));
-	unsigned int code = MAY_LJMP(luaL_checkinteger(L, 2));
-	const char *str = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL));
-	const struct ist reason = ist2(str, (str ? strlen(str) : 0));
+	struct http_msg *msg;
+	struct htx *htx;
 
-	if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s))
-		WILL_LJMP(lua_error(L));
+	MAY_LJMP(check_args(L, 1, "set_eom"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	htx = htxbuf(&msg->chn->buf);
+	htx->flags |= HTX_FL_EOM;
+	return 0;
+}
 
-	http_res_set_status(code, reason, htxn->s);
+/* Unset EOM flag on the HTX message.
+ *
+ * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't
+ *       really know how to do without this feature.
+ */
+__LJMP static int hlua_http_msg_unset_eom(lua_State *L)
+{
+	struct http_msg *msg;
+	struct htx *htx;
+
+	MAY_LJMP(check_args(L, 1, "set_eom"));
+	msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+	htx = htxbuf(&msg->chn->buf);
+	htx->flags &= ~HTX_FL_EOM;
 	return 0;
 }
 
@@ -10343,6 +11352,55 @@
 	/* Register previous table in the registry with reference and named entry. */
 	class_http_ref = hlua_register_metatable(L, CLASS_HTTP);
 
+	/*
+	 *
+	 * Register class HTTPMessage
+	 *
+	 */
+
+	/* Create and fill the metatable. */
+	lua_newtable(L);
+
+	/* Create and fille the __index entry. */
+	lua_pushstring(L, "__index");
+	lua_newtable(L);
+
+	/* Register Lua functions. */
+	hlua_class_function(L, "is_resp",     hlua_http_msg_is_resp);
+	hlua_class_function(L, "get_stline",  hlua_http_msg_get_stline);
+	hlua_class_function(L, "get_headers", hlua_http_msg_get_headers);
+	hlua_class_function(L, "del_header",  hlua_http_msg_del_hdr);
+	hlua_class_function(L, "rep_header",  hlua_http_msg_rep_hdr);
+	hlua_class_function(L, "rep_value",   hlua_http_msg_rep_val);
+	hlua_class_function(L, "add_header",  hlua_http_msg_add_hdr);
+	hlua_class_function(L, "set_header",  hlua_http_msg_set_hdr);
+	hlua_class_function(L, "set_method",  hlua_http_msg_set_meth);
+	hlua_class_function(L, "set_path",    hlua_http_msg_set_path);
+	hlua_class_function(L, "set_query",   hlua_http_msg_set_query);
+	hlua_class_function(L, "set_uri",     hlua_http_msg_set_uri);
+	hlua_class_function(L, "set_status",  hlua_http_msg_set_status);
+	hlua_class_function(L, "is_full",     hlua_http_msg_is_full);
+	hlua_class_function(L, "may_recv",    hlua_http_msg_may_recv);
+	hlua_class_function(L, "eom",         hlua_http_msg_is_eom);
+	hlua_class_function(L, "input",       hlua_http_msg_get_in_len);
+	hlua_class_function(L, "output",      hlua_http_msg_get_out_len);
+
+	hlua_class_function(L, "body",        hlua_http_msg_get_body);
+	hlua_class_function(L, "set",         hlua_http_msg_set_data);
+	hlua_class_function(L, "remove",      hlua_http_msg_del_data);
+	hlua_class_function(L, "append",      hlua_http_msg_append);
+	hlua_class_function(L, "prepend",     hlua_http_msg_prepend);
+	hlua_class_function(L, "insert",      hlua_http_msg_insert_data);
+	hlua_class_function(L, "set_eom",     hlua_http_msg_set_eom);
+	hlua_class_function(L, "unset_eom",   hlua_http_msg_unset_eom);
+
+	hlua_class_function(L, "send",        hlua_http_msg_send);
+	hlua_class_function(L, "forward",     hlua_http_msg_forward);
+
+	lua_rawset(L, -3);
+
+	/* Register previous table in the registry with reference and named entry. */
+	class_http_msg_ref = hlua_register_metatable(L, CLASS_HTTP_MSG);
 	/*
 	 *
 	 * Register class AppletTCP