]> source.dussan.org Git - rspamd.git/commitdiff
* Add lua http support that makes it easy to send GET and POST request to HTTP server...
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Thu, 9 Jun 2011 16:47:35 +0000 (20:47 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Thu, 9 Jun 2011 16:47:35 +0000 (20:47 +0400)
src/lua/CMakeLists.txt
src/lua/lua_common.c
src/lua/lua_common.h
src/lua/lua_http.c [new file with mode: 0644]

index 5e3ba9ca53db7fed91f0e8f45c53d3fd94d23e21..04fa393ad51cb6afd2933ac7175811543f7b3ba0 100644 (file)
@@ -7,7 +7,8 @@ SET(LUASRC                        lua_common.c
                                          lua_cfg_file.c
                                          lua_regexp.c
                                          lua_cdb.c
-                                         lua_xmlrpc.c)
+                                         lua_xmlrpc.c
+                                         lua_http.c)
 
 ADD_LIBRARY(rspamd_lua STATIC ${LUASRC})
 TARGET_LINK_LIBRARIES(rspamd_lua ${LUALIB})
index 2a1783cfb2d61f5635e747d093aca38004779e76..05bea312ce34d36b99e7007eeddb8138464cb7d9 100644 (file)
@@ -240,6 +240,7 @@ init_lua (struct config_file *cfg)
        (void)luaopen_glib_regexp (L);
        (void)luaopen_cdb (L);
        (void)luaopen_xmlrpc (L);
+       (void)luaopen_http (L);
        cfg->lua_state = L;
        memory_pool_add_destructor (cfg->cfg_pool, (pool_destruct_func)lua_close, L);
 
index 82deb0685a9a92451c36c0b4b8fca1b82682492a..a78d168fce2de75f1cec1dbd7db6745e07d8e1aa 100644 (file)
@@ -38,6 +38,7 @@ gint luaopen_statfile (lua_State * L);
 gint luaopen_glib_regexp (lua_State *L);
 gint luaopen_cdb (lua_State *L);
 gint luaopen_xmlrpc (lua_State * L);
+gint luaopen_http (lua_State * L);
 void init_lua (struct config_file *cfg);
 gboolean init_lua_filters (struct config_file *cfg);
 
diff --git a/src/lua/lua_http.c b/src/lua/lua_http.c
new file mode 100644 (file)
index 0000000..25cfa69
--- /dev/null
@@ -0,0 +1,443 @@
+/* Copyright (c) 2010, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *       * Redistributions of source code must retain the above copyright
+ *         notice, this list of conditions and the following disclaimer.
+ *       * Redistributions in binary form must reproduce the above copyright
+ *         notice, this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "lua_common.h"
+#include "../buffer.h"
+#include "../dns.h"
+
+#define MAX_HEADERS_SIZE 8192
+
+LUA_FUNCTION_DEF (http, make_post_request);
+LUA_FUNCTION_DEF (http, make_get_request);
+
+static const struct luaL_reg    httplib_m[] = {
+       LUA_INTERFACE_DEF (http, make_post_request),
+       LUA_INTERFACE_DEF (http, make_get_request),
+       {"__tostring", lua_class_tostring},
+       {NULL, NULL}
+};
+
+struct lua_http_header {
+       gchar *name;
+       gchar *value;
+};
+
+struct lua_http_ud {
+       gint parser_state;
+       struct worker_task *task;
+       lua_State *L;
+       const gchar *callback;
+       gchar *req_buf;
+       gint req_len;
+       gint port;
+       gint timeout;
+       gint code;
+       gint fd;
+       rspamd_io_dispatcher_t *io_dispatcher;
+       gint rep_len;
+       GList *headers;
+};
+
+static struct worker_task      *
+lua_check_task (lua_State * L)
+{
+       void                           *ud = luaL_checkudata (L, 1, "rspamd{task}");
+       luaL_argcheck (L, ud != NULL, 1, "'task' expected");
+       return *((struct worker_task **)ud);
+}
+
+static void
+lua_http_push_error (gint code, struct lua_http_ud *ud)
+{
+       struct worker_task            **ptask;
+
+       /* Push error */
+       lua_getglobal (ud->L, ud->callback);
+       ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *));
+       lua_setclass (ud->L, "rspamd{task}", -1);
+
+       *ptask = ud->task;
+       /* Code */
+       lua_pushnumber (ud->L, code);
+       /* Headers */
+       lua_pushnil (ud->L);
+       /* Reply */
+       lua_pushnil (ud->L);
+       if (lua_pcall (ud->L, 4, 0, 0) != 0) {
+               msg_info ("call to %s failed: %s", ud->callback, lua_tostring (ud->L, -1));
+       }
+
+       if (ud->headers != NULL) {
+               g_list_free (ud->headers);
+               ud->headers = NULL;
+       }
+
+       ud->parser_state = 3;
+
+       ud->task->save.saved--;
+       if (ud->task->save.saved == 0) {
+               /* Call other filters */
+               ud->task->save.saved = 1;
+               process_filters (ud->task);
+       }
+}
+
+static void
+lua_http_push_reply (f_str_t *in, struct lua_http_ud *ud)
+{
+       GList                          *cur;
+       struct lua_http_header         *header;
+       struct worker_task            **ptask;
+
+       /* Push error */
+       lua_getglobal (ud->L, ud->callback);
+       ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *));
+       lua_setclass (ud->L, "rspamd{task}", -1);
+
+       *ptask = ud->task;
+       /* Code */
+       lua_pushnumber (ud->L, ud->code);
+       /* Headers */
+       lua_newtable (ud->L);
+       cur = ud->headers;
+
+       while (cur) {
+               header = cur->data;
+               lua_pushstring (ud->L, header->name);
+               lua_pushstring (ud->L, header->value);
+               lua_settable (ud->L, -3);
+               cur = g_list_next (cur);
+       }
+       /* Reply */
+       lua_pushlstring (ud->L, in->begin, in->len);
+
+       if (lua_pcall (ud->L, 4, 0, 0) != 0) {
+               msg_info ("call to %s failed: %s", ud->callback, lua_tostring (ud->L, -1));
+       }
+
+       if (ud->headers != NULL) {
+               g_list_free (ud->headers);
+               ud->headers = NULL;
+       }
+
+       ud->task->save.saved--;
+       if (ud->task->save.saved == 0) {
+               /* Call other filters */
+               ud->task->save.saved = 1;
+               process_filters (ud->task);
+       }
+}
+
+
+/*
+ * Parsing utils
+ */
+static gboolean
+lua_http_parse_first_line (struct lua_http_ud *ud, f_str_t *in)
+{
+       const gchar                    *p;
+
+       /* Assume first line is like this: HTTP/1.1 200 OK */
+       if (in->len < sizeof ("HTTP/1.1 OK") + 2) {
+               msg_info ("bad http string: %V", in);
+               return FALSE;
+       }
+
+       p = in->begin + sizeof("HTTP/1.1 ") - 1;
+       ud->code = strtoul (p, NULL, 10);
+
+       ud->parser_state = 1;
+       return TRUE;
+}
+
+static gboolean
+lua_http_parse_header_line (struct lua_http_ud *ud, f_str_t *in)
+{
+       const gchar                    *p = in->begin;
+       struct lua_http_header         *new;
+
+       while (p < in->begin + in->len) {
+               if (*p == ':') {
+                       break;
+               }
+               p ++;
+       }
+
+       if (*p != ':') {
+               return FALSE;
+       }
+       /* Copy name */
+       new = memory_pool_alloc (ud->task->task_pool, sizeof (struct lua_http_header));
+       new->name = memory_pool_alloc (ud->task->task_pool, p - in->begin + 1);
+       rspamd_strlcpy (new->name, in->begin, p - in->begin + 1);
+
+       p ++;
+       /* Copy value */
+       while (p < in->begin + in->len && g_ascii_isspace (*p)) {
+               p ++;
+       }
+       new->value = memory_pool_alloc (ud->task->task_pool, in->begin + in->len - p + 1);
+       rspamd_strlcpy (new->value, p, in->begin + in->len - p + 1);
+
+       /* Check content-length */
+       if (ud->rep_len == 0 && g_ascii_strcasecmp (new->name, "content-length") == 0) {
+               ud->rep_len = strtoul (new->value, NULL, 10);
+       }
+
+       /* Insert a header to the list */
+       ud->headers = g_list_prepend (ud->headers, new);
+
+       return TRUE;
+}
+
+/* Read callback */
+static gboolean
+lua_http_read_cb (f_str_t * in, void *arg)
+{
+       struct lua_http_ud             *ud = arg;
+
+       switch (ud->parser_state) {
+       case 0:
+               /* Parse first line */
+               return lua_http_parse_first_line (ud, in);
+       case 1:
+               if (ud->code != 200) {
+                       lua_http_push_error (ud->code, ud);
+                       return FALSE;
+               }
+               /* Parse header */
+               if (in->len == 0) {
+                       /* Final line */
+                       if (ud->rep_len == 0) {
+                               /* No content-length */
+                               msg_info ("http reply contains no content-length header");
+                               lua_http_push_error (450, ud);
+                               return FALSE;
+                       }
+                       else {
+                               ud->parser_state = 2;
+                               rspamd_set_dispatcher_policy (ud->io_dispatcher, BUFFER_CHARACTER, ud->rep_len);
+                       }
+               }
+               else {
+                       return lua_http_parse_header_line (ud, in);
+               }
+               break;
+       case 2:
+               /* Get reply */
+               lua_http_push_reply (in, ud);
+               rspamd_remove_dispatcher (ud->io_dispatcher);
+               close (ud->fd);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+lua_http_err_cb (GError * err, void *arg)
+{
+       struct lua_http_ud             *ud = arg;
+       msg_info ("abnormally closing connection to http server error: %s", err->message);
+       g_error_free (err);
+
+       if (ud->parser_state != 3) {
+               lua_http_push_error (500, ud);
+       }
+       rspamd_remove_dispatcher (ud->io_dispatcher);
+       close (ud->fd);
+}
+
+static void
+lua_http_dns_callback (struct rspamd_dns_reply *reply, gpointer arg)
+{
+       struct lua_http_ud             *ud = arg;
+       union rspamd_reply_element     *elt;
+       struct in_addr                  ina;
+       struct timeval                  tv;
+
+       if (reply->code != DNS_RC_NOERROR) {
+               lua_http_push_error (450, ud);
+               return;
+       }
+
+       /* Create socket to server */
+       elt = reply->elements->data;
+       memcpy (&ina, &elt->a.addr[0], sizeof (struct in_addr));
+
+       ud->fd = make_tcp_socket (&ina, ud->port, FALSE, TRUE);
+
+       if (ud->fd == -1) {
+               lua_http_push_error (450, ud);
+               return;
+       }
+
+       /* Create dispatcher for HTTP protocol */
+       msec_to_tv (ud->timeout, &tv);
+       ud->io_dispatcher = rspamd_create_dispatcher (ud->fd, BUFFER_LINE, lua_http_read_cb, NULL, lua_http_err_cb,
+                       &tv, ud);
+       /* Write request */
+
+       if (!rspamd_dispatcher_write (ud->io_dispatcher, ud->req_buf, ud->req_len, FALSE, TRUE)) {
+               lua_http_push_error (450, ud);
+               rspamd_remove_dispatcher (ud->io_dispatcher);
+               close (ud->fd);
+               return;
+       }
+}
+
+/**
+ * Common request function
+ */
+static gint
+lua_http_make_request_common (lua_State *L, struct worker_task *task, const gchar *callback,
+               const gchar *hostname, const gchar *path, const gchar *data, gint top)
+{
+       gint                           r, s, datalen;
+       struct lua_http_ud                        *ud;
+
+       /* Calculate buffer size */
+       datalen = (data != NULL) ? strlen (data) : 0;
+       s = MAX_HEADERS_SIZE + sizeof (CRLF) * 3 + strlen (hostname) + strlen (path) + datalen
+                       + sizeof ("POST HTTP/1.1");
+
+       ud = memory_pool_alloc0 (task->task_pool, sizeof (struct lua_http_ud));
+       ud->L = L;
+       ud->task = task;
+       /* Preallocate buffer */
+       ud->req_buf = memory_pool_alloc (task->task_pool, s);
+       ud->callback = callback;
+
+       /* Print request */
+       r = rspamd_snprintf (ud->req_buf, s, "%s %s HTTP/1.1" CRLF
+                                                                                "Connection: close" CRLF
+                                                                                "Host: %s" CRLF,
+                                                                                (data != NULL) ? "POST" : "GET", path, hostname);
+       if (datalen > 0) {
+               r += rspamd_snprintf (ud->req_buf + r, s - r, "Content-Length: %d" CRLF, datalen);
+       }
+       /* Now assume that we have a table with headers at the top of the stack */
+
+       if (lua_gettop (L) > top && lua_istable (L, top + 1)) {
+               /* Add headers */
+               lua_pushnil (L);  /* first key */
+               while (lua_next (L, top + 1) != 0) {
+                       r += rspamd_snprintf (ud->req_buf + r, s - r, "%s: %s" CRLF, lua_tostring (L, -2), lua_tostring (L, -1));
+                       lua_pop(L, 1);
+               }
+       }
+       /* Now check port and timeout */
+       if (lua_gettop (L) > top + 1) {
+               ud->port = lua_tonumber (L, top + 2);
+       }
+       else {
+               ud->port = 80;
+       }
+       if (lua_gettop (L) > top + 2) {
+               ud->timeout = lua_tonumber (L, top + 3);
+       }
+       else {
+               /* Assume default timeout as 1000 msec */
+               ud->timeout = 1000;
+       }
+
+       if (datalen > 0) {
+               r += rspamd_snprintf (ud->req_buf + r, s - r, CRLF "%s", data);
+       }
+       else {
+               r += rspamd_snprintf (ud->req_buf + r, s - r, CRLF);
+       }
+
+       ud->req_len = r;
+
+       /* Resolve hostname */
+       if (make_dns_request (task->resolver, task->s, task->task_pool, lua_http_dns_callback, ud,
+                       DNS_REQUEST_A, hostname)) {
+               task->dns_requests ++;
+               task->save.saved++;
+       }
+
+       return 0;
+}
+
+/*
+ * Typical usage:
+ * rspamd_http.post_request(task, 'callback', 'hostname', 'path', 'data'[, headers -> { name = 'value' }])
+ */
+static gint
+lua_http_make_post_request (lua_State *L)
+{
+       struct worker_task            *task = lua_check_task (L);
+       const gchar                   *hostname, *path, *data, *callback;
+
+       /* Now extract hostname, path and data */
+
+       callback = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 2));
+       hostname = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 3));
+       path = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 4));
+       data = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 5));
+
+       if (callback != NULL && hostname != NULL && path != NULL && data != NULL) {
+               return lua_http_make_request_common (L, task, callback, hostname, path, data, 5);
+       }
+       else {
+               msg_info ("invalid arguments number");
+       }
+
+       return 0;
+}
+
+/*
+ * Typical usage:
+ * rspamd_http.get_request(task, 'callback', 'hostname', 'path'[, headers -> { name = 'value' }])
+ */
+static gint
+lua_http_make_get_request (lua_State *L)
+{
+       struct worker_task            *task = lua_check_task (L);
+       const gchar                   *hostname, *path, *callback;
+
+       /* Now extract hostname, path and data */
+
+       callback = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 2));
+       hostname = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 3));
+       path = memory_pool_strdup (task->task_pool, luaL_checkstring (L, 4));
+
+       if (callback != NULL && hostname != NULL && path != NULL) {
+               return lua_http_make_request_common (L, task, callback, hostname, path, NULL, 4);
+       }
+       else {
+               msg_info ("invalid arguments number");
+       }
+
+       return 0;
+}
+
+gint
+luaopen_http (lua_State * L)
+{
+
+       luaL_openlib (L, "rspamd_http", httplib_m, 0);
+
+       return 1;
+}