Parcourir la source

Merge pull request #480 from grumvalski/async_http_mod

http_async_client: non-blocking async HTTP client module
Federico Cabiddu il y a 9 ans
Parent
commit
3ba6c4451a

+ 32 - 0
modules/http_async_client/Makefile

@@ -0,0 +1,32 @@
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=http_async_client.so
+
+#ifeq ($(CROSS_COMPILE),)
+#CURL_BUILDER=$(shell \
+#	if pkg-config --exists libcurl; then \
+#		echo 'pkg-config libcurl'; \
+#	else \
+#		which curl-config; \
+#	fi)
+#endif
+
+
+ifneq ($(CURL_BUILDER),)
+	DEFS += $(shell $(CURL_BUILDER) --cflags)
+	LIBS += $(shell $(CURL_BUILDER) --libs)
+else	
+	DEFS+=-I$(LOCALBASE)/include -I$(SYSBASE)/include
+	LIBS+=-L$(LOCALBASE)/lib -L$(SYSBASE)/lib -lcurl -levent
+endif
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srutils/srutils
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+
+include ../../Makefile.modules

+ 567 - 0
modules/http_async_client/README

@@ -0,0 +1,567 @@
+ASYNC_HTTP Module
+
+Federico Cabiddu
+
+   <[email protected]>
+
+Giacomo Vacca
+
+   <[email protected]>
+
+Camille Oudot
+
+   Orange
+   <[email protected]>
+
+Edited by
+
+Federico Cabiddu
+
+   <[email protected]>
+
+   Copyright © 2016 Federico Cabiddu
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. workers (int)
+              3.2. connection_timeout (str)
+              3.3. hash_size (str)
+              3.4. tlsversion (int)
+              3.5. tlsverifyhost (int)
+              3.6. tlsverifypeer (int)
+              3.7. curl_verbose (int)
+              3.8. memory_manager (string)
+              3.9. tlsclientcert (int)
+              3.10. tlsclientkey (int)
+              3.11. tlscapath (int)
+
+        4. Functions
+
+              4.1. http_async_query(url, [post_data], route_name)
+              4.2. http_async_suspend(suspend)
+              4.3. http_verify_host(verify)
+              4.4. http_verify_peer(verify)
+              4.5. http_set_timeout(timeout)
+              4.6. http_append_header(header)
+              4.7. http_set_method(method)
+              4.8. http_set_ssl_cert(path)
+              4.9. http_set_ssl_key(path)
+              4.10. http_set_ca_path(path)
+
+        5. Pseudo Variables
+        6. Statistics
+
+              6.1. requests
+              6.2. replies
+              6.3. errors
+              6.4. timeouts
+
+   List of Examples
+
+   1.1. Set workers parameter
+   1.2. Set connection_timeout parameter
+   1.3. Set hash_size parameter
+   1.4. Set tlsversion parameter
+   1.5. Set tlsverifyhost parameter
+   1.6. Set tlsverifypeer parameter
+   1.7. Set curl_verbose parameter
+   1.8. Set memory_manager parameter
+   1.9. Set tlsclientcert parameter
+   1.10. Set tlsclientkey parameter
+   1.11. Set tlscapath parameter
+   1.12. http_async_query() usage
+   1.13. http_async_suspend() usage
+   1.14. http_verify_host() usage
+   1.15. http_verify_peer() usage
+   1.16. http_set_timeout() usage
+   1.17. http_append_header() usage
+   1.18. http_set_method() usage
+   1.19. http_set_ssl_cert() usage
+   1.20. http_set_ssl_key() usage
+   1.21. http_set_ca_path() usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. workers (int)
+        3.2. connection_timeout (str)
+        3.3. hash_size (str)
+        3.4. tlsversion (int)
+        3.5. tlsverifyhost (int)
+        3.6. tlsverifypeer (int)
+        3.7. curl_verbose (int)
+        3.8. memory_manager (string)
+        3.9. tlsclientcert (int)
+        3.10. tlsclientkey (int)
+        3.11. tlscapath (int)
+
+   4. Functions
+
+        4.1. http_async_query(url, [post_data], route_name)
+        4.2. http_async_suspend(suspend)
+        4.3. http_verify_host(verify)
+        4.4. http_verify_peer(verify)
+        4.5. http_set_timeout(timeout)
+        4.6. http_append_header(header)
+        4.7. http_set_method(method)
+        4.8. http_set_ssl_cert(path)
+        4.9. http_set_ssl_key(path)
+        4.10. http_set_ca_path(path)
+
+   5. Pseudo Variables
+   6. Statistics
+
+        6.1. requests
+        6.2. replies
+        6.3. errors
+        6.4. timeouts
+
+1. Overview
+
+   This module performs asynchronous HTTP queries.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * tm - Transaction module
+     * pv - Pseudo-Variables module
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * libcurl libev
+
+3. Parameters
+
+   3.1. workers (int)
+   3.2. connection_timeout (str)
+   3.3. hash_size (str)
+   3.4. tlsversion (int)
+   3.5. tlsverifyhost (int)
+   3.6. tlsverifypeer (int)
+   3.7. curl_verbose (int)
+   3.8. memory_manager (string)
+   3.9. tlsclientcert (int)
+   3.10. tlsclientkey (int)
+   3.11. tlscapath (int)
+
+3.1. workers (int)
+
+   Number of worker processes to be started to send HTTP requests and
+   asynchronously handle responses.
+
+   Default value is 1.
+
+   Example 1.1. Set workers parameter
+...
+modparam("async_http", "workers", 2)
+...
+
+3.2. connection_timeout (str)
+
+   Defines in milliseconds how long Kamailio waits for response from HTTP
+   server.
+
+   Default value is 500ms.
+
+   Example 1.2. Set connection_timeout parameter
+...
+modparam("async_http", "connection_timeout", 1000)
+...
+
+3.3. hash_size (str)
+
+   The size of the hash table internally used to keep the requests. A
+   larger table is much faster but consumes more memory. The hash size
+   must be a power of two, otherwise it will be rounded down to the
+   nearest power of two.
+
+   Default value is 2048.
+
+   Example 1.3. Set hash_size parameter
+...
+modparam("async_http", "hash_size", 1024)
+...
+
+3.4. tlsversion (int)
+
+   For HTTPS connections, what's the preferred SSL version.
+   http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html
+
+   Default value is 0 (default SSL version).
+
+   Example 1.4. Set tlsversion parameter
+...
+modparam("async_http", "tlsversion", 6)
+...
+
+3.5. tlsverifyhost (int)
+
+   For HTTPS connections, whether the client should verify the server
+   host. http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
+
+   Default value is 2 (enabled).
+
+   Example 1.5. Set tlsverifyhost parameter
+...
+modparam("async_http", "tlsverifyhost", 0)
+...
+
+3.6. tlsverifypeer (int)
+
+   For HTTPS connections, whether the client should verify the server
+   identity. http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
+
+   Default value is 1 (enabled).
+
+   Example 1.6. Set tlsverifypeer parameter
+...
+modparam("async_http", "tlsverifypeer", 0)
+...
+
+3.7. curl_verbose (int)
+
+   If defined to a non-zero value, extra informations from cURL (request
+   and response headers) will be included in the kamailio logs, with
+   LM_INFO priority.
+
+   Default value is 0 (disabled).
+
+   Example 1.7. Set curl_verbose parameter
+...
+modparam("async_http", "curl_verbose", 1)
+...
+
+3.8. memory_manager (string)
+
+   Choose the memory manager used by curl:
+     * shm: curl will use kamailio's SHM pool and memory manager
+     * sys: curl will use the system memory amd memory manager (malloc,
+       free, ...)
+
+   Note: if this module is used in conjunction with another module using
+   libcurl, this parameter will have no effect, and curl will likely use
+   the system memory allocator by default.
+
+   Default value "shm"
+
+   Example 1.8. Set memory_manager parameter
+...
+modparam("async_http", "memory_manager", "sys")
+...
+
+3.9. tlsclientcert (int)
+
+   For HTTPS connections, the file path of the TLS client certificate to
+   be used http://curl.haxx.se/libcurl/c/CURLOPT_SSLCERT.html
+
+   Default value is NULL (not used). Default type is PEM.
+
+   Example 1.9. Set tlsclientcert parameter
+...
+modparam("async_http", "tlsclientcert", "/etc/kamailio/ssl/clientcert.pem")
+...
+
+3.10. tlsclientkey (int)
+
+   For HTTPS connections, the file path of the TLS client certificate key
+   http://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
+
+   Default value is NULL (not used).
+
+   Example 1.10. Set tlsclientkey parameter
+...
+modparam("async_http", "tlsclientkey", "/etc/kamailio/ssl/clientcert.key")
+...
+
+3.11. tlscapath (int)
+
+   The path of the directory holding the CA certificates.
+   http://curl.haxx.se/libcurl/c/CURLOPT_CAPATH.html
+
+   Default value is NULL..
+
+   Example 1.11. Set tlscapath parameter
+...
+modparam("async_http", "tlscapath", "/etc/kamailio/ssl/ca/")
+...
+
+4. Functions
+
+   4.1. http_async_query(url, [post_data], route_name)
+   4.2. http_async_suspend(suspend)
+   4.3. http_verify_host(verify)
+   4.4. http_verify_peer(verify)
+   4.5. http_set_timeout(timeout)
+   4.6. http_append_header(header)
+   4.7. http_set_method(method)
+   4.8. http_set_ssl_cert(path)
+   4.9. http_set_ssl_key(path)
+   4.10. http_set_ca_path(path)
+
+4.1.  http_async_query(url, [post_data], route_name)
+
+   Sends HTTP(S) request asyncronously to the URL given in “url”
+   parameter, which is a string that may contain pseudo variables.
+
+   Unless a specific HTTP method was specified using
+   http_async_set_method(), it defaults to a GET request, or to a POST
+   request if “post_data” is issued as second argument.
+
+   Parameter “post_data”, optional, which is sent as the body of the
+   request, may also contain pseudo variables.
+
+   Parameter “route_name” defines the route to be executed upon reception
+   of HTTP reply, on error or on timeout. If a transaction exists before
+   calling http_async_query(), it will be paused and resumed in this
+   route, while the routing script execution will be stopped. If executed
+   in a transactionless context, or if http_async_suspend_transaction() is
+   used to not suspend the transaction, the routing script execution will
+   continue and the query result will be available in “route_name”.
+
+   Return value: 0 (stop script execution) on success in transaction
+   context, 1 (continue script execution) in transaction-less context or
+   if http_async_suspend_transaction(0) is used, -1 on error.
+
+   This function can be used from ANY_ROUTE.
+
+   This method is executed asynchronously. The HTTP return code, body and
+   error are returned in the module-specific $http_* PVs (see below). See
+   example on how to retrieve return values.
+
+   Example 1.12. http_async_query() usage
+...
+# create a transaction to be paused, and resumed in route[HTTP_REPLY]
+t_newtran();
+# GET
+http_async_query("http://example.com/test.php?r_uri=$rU&f_uri=$fU", "HTTP_REPLY"
+);
+...
+# POST
+http_async_query("http://example.com/test.php", "{'r_uri':'$rU', 'f_uri':'$fU'}"
+, "HTTP_REPLY");
+...
+route[HTTP_REPLY] {
+    if ($http_ok) {
+        xlog("L_INFO", "route[HTTP_REPLY]: status $http_rs\n");
+        xlog("L_INFO", "route[HTTP_REPLY]: body   $http_rb\n");
+    } else {
+        xlog("L_INFO", "route[HTTP_REPLY]: error  $http_error)\n");
+    }
+}
+...
+
+4.2.  http_async_suspend(suspend)
+
+   In a transaction context if the transaction must be suspended and
+   script execution stopped.
+
+   Parameter “suspend” set to "1" to suspend the transaction, "0" to not
+   suspend and continue with script execution. Default: 1 (transaction
+   suspended).
+
+   Example 1.13. http_async_suspend() usage
+...
+t_newtran();
+http_async_suspend(0);
+# the transaction won't be suspended for the next query
+http_async_query("http://example.com/test.php", "HTTP_REPLY");
+xlog("L_INFO", "query sent\n");
+t_reply("200", "Ok");
+
+...
+
+4.3.  http_verify_host(verify)
+
+   For the next HTTPS connection, whether the client should verify the
+   server host.
+
+   Parameter “verify” set to "1" to enable the host verification, "0" to
+   disable. Default: the global value set as verify_host module parameter.
+
+   Example 1.14. http_verify_host() usage
+...
+http_verify_host("0");
+# host verification is disabled for the next query
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+4.4.  http_verify_peer(verify)
+
+   For the next HTTPS connection, whether the client should verify the
+   server identity.
+
+   Parameter “verify” set to "1" to enable the identity verification, "0"
+   to disable. Default: the global value set as verify_peer module
+   parameter.
+
+   Example 1.15. http_verify_peer() usage
+...
+http_verify_peer("0");
+# server identity verification is disabled for the next query
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+4.5.  http_set_timeout(timeout)
+
+   For the next HTTP query, set the response timeout.
+
+   Parameter “timeout” string representing the timeout in milliseconds.
+   Default: the global value set as http_timeout module parameter.
+
+   Example 1.16. http_set_timeout() usage
+...
+http_set_timeout("200");
+# the server must respond in maximum 200ms, otherwise the query will fail
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+4.6.  http_append_header(header)
+
+   Set/remove/replace a header in the next HTTP query.
+
+   From libcurl's documentation:
+
+   “If you add a header that is otherwise generated and used by libcurl
+   internally, your added one will be used instead. If you add a header
+   with no content as in 'Accept:' (no data on the right side of the
+   colon), the internally used header will get disabled. With this option
+   you can add new headers, replace internal headers and remove internal
+   headers. To add a header with no content (nothing to the right side of
+   the colon), use the form 'MyHeader;' (note the ending semicolon).”
+
+   “The headers included in the linked list must not be CRLF-terminated,
+   because libcurl adds CRLF after each header item.”
+
+   Parameter “header” string representing the header to pass to libcurl
+   for the next query.
+
+   Example 1.17. http_append_header() usage
+...
+http_append_header("X-Sip-Call-Id: $ci");
+# a new 'X-Sip-Call-Id' header will be added to the next query
+http_append_header("Content-Type": application/json");
+# the curl default 'application/x-www-form-urlencoded' Content-Type will be repl
+aced
+http_query("https://example.com/test.php", "{'foo': 'bar'}", "HTTP_REPLY");
+...
+
+4.7.  http_set_method(method)
+
+   For the next HTTP query, set the method.
+
+   Parameter “method” string representing the method (verb): either "GET",
+   "POST", "PUT" or "DELETE" (these are the only supported methods).
+   (Note: if http_set_method() is not called before a query, curl will use
+   GET, or POST if a body is specified)
+
+   Example 1.18. http_set_method() usage
+...
+http_set_method("PUT");
+# the next query will be a HTTP PUT request
+http_query("https://example.com/test.php", "{'foo': 'bar'}", "HTTP_REPLY");
+...
+
+4.8.  http_set_ssl_cert(path)
+
+   For the next HTTPS connection, what client certificate to use.
+
+   Default: the global value set as ssl_cert module parameter.
+
+   Example 1.19. http_set_ssl_cert() usage
+...
+http_set_ssl_cert("/etc/kamailio/ssl/cert.pem");
+# Next query will use the client cert above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+4.9.  http_set_ssl_key(path)
+
+   For the next HTTPS connection, what client certificate key to use.
+
+   Default: the global value set as ssl_key module parameter.
+
+   Example 1.20. http_set_ssl_key() usage
+...
+http_set_ssl_key("/etc/kamailio/ssl/cert.key");
+# Next query will use the client cert key above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+4.10.  http_set_ca_path(path)
+
+   For the next HTTP connection, what CA certificate files to use.
+
+   Default: the global value set as ca_path module parameter.
+
+   Example 1.21. http_set_ca_path() usage
+...
+http_set_ssl_key("/etc/kamailio/ssl/ca/");
+# Next query will use the CA certs above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+
+5. Pseudo Variables
+
+   The following pseudo variables can only be used in the callback routes
+   executed by http_async_query()
+     * $http_ok: 1 if cURL executed the request successfully, 0 otherwise
+       (check $ah_err for details)
+     * $http_err: cURL error string if an error occured, $null otherwise
+     * $http_rs: http status
+     * $http_rr: http reason phrase
+     * $http_hdr(Name): value of the Name header (the $(ah_hdr(Name)[N])
+       syntax can also be used, check the SIP $hdr() PV documentation for
+       details)
+     * $http_mb and $http_ml: HTTP response buffer (including headers) and
+       length
+     * $http_rb and $http_bs: HTTP response body and body length
+
+6. Statistics
+
+   6.1. requests
+   6.2. replies
+   6.3. errors
+   6.4. timeouts
+
+6.1. requests
+
+   The number of http requests sent.
+
+6.2. replies
+
+   The number of received http replies.
+
+6.3. errors
+
+   The number of errors.
+
+6.4. timeouts
+
+   The number of timed out requests.

+ 448 - 0
modules/http_async_client/async_http.c

@@ -0,0 +1,448 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+#include <event2/event.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../cfg/cfg_struct.h"
+#include "../../lib/kcore/faked_msg.h"
+#include "../../modules/tm/tm_load.h"
+
+#include "async_http.h"
+
+/* tm */
+extern struct tm_binds tmb;
+
+struct sip_msg *ah_reply = NULL;
+str ah_error = {NULL, 0};
+
+async_http_worker_t *workers;
+int num_workers = 1;
+
+struct query_params ah_params;
+
+int async_http_init_worker(int prank, async_http_worker_t* worker)
+{
+	LM_DBG("initializing worker process: %d\n", prank);
+	worker->evbase = event_base_new();
+	LM_DBG("base event %p created\n", worker->evbase);
+
+	worker->g = shm_malloc(sizeof(struct http_m_global));
+	memset(worker->g, 0, sizeof(http_m_global_t));
+	LM_DBG("initialized global struct %p\n", worker->g);
+
+	init_socket(worker);
+
+	LM_INFO("started worker process: %d\n", prank);
+
+	return 0;
+}
+
+void async_http_run_worker(async_http_worker_t* worker)
+{
+	init_http_multi(worker->evbase, worker->g);
+	event_base_dispatch(worker->evbase);
+}
+
+int async_http_init_sockets(async_http_worker_t *worker)
+{
+	if (socketpair(PF_UNIX, SOCK_DGRAM, 0, worker->notication_socket) < 0) {
+		LM_ERR("opening tasks dgram socket pair\n");
+		return -1;
+	}
+	LM_INFO("inter-process event notification sockets initialized\n");
+	return 0;
+}
+
+void async_http_cb(struct http_m_reply *reply, void *param)
+{
+	async_query_t *aq;
+	cfg_action_t *act;
+	unsigned int tindex;
+	unsigned int tlabel;
+	struct cell *t = NULL;
+	sip_msg_t *fmsg;
+
+	if (reply->result != NULL) {
+		LM_DBG("query result = %.*s [%d]\n", reply->result->len, reply->result->s, reply->result->len);
+	}
+
+	/* clean process-local result variables */
+	ah_error.s = NULL;
+	ah_error.len = 0;
+	memset(ah_reply, 0, sizeof(struct sip_msg));
+
+	/* set process-local result variables */
+	if (reply->result == NULL) {
+		/* error */
+		ah_error.s = reply->error;
+		ah_error.len = strlen(ah_error.s);
+	} else {
+		/* success */
+		ah_reply->buf = reply->result->s;
+		ah_reply->len = reply->result->len;
+
+		if (parse_msg(reply->result->s, reply->result->len, ah_reply) != 0) {
+			LM_DBG("failed to parse the http_reply\n");
+		} else {
+			LM_DBG("successfully parsed http reply %p\n", ah_reply);
+		}
+	}
+
+	aq = param;
+	act = (cfg_action_t*)aq->param;
+	if (aq->query_params.suspend_transaction) {
+		tindex = aq->tindex;
+		tlabel = aq->tlabel;
+
+		if (tmb.t_lookup_ident(&t, tindex, tlabel) < 0) {
+			LM_ERR("transaction not found %d:%d\n", tindex, tlabel);
+			LM_DBG("freeing query %p\n", aq);
+			free_async_query(aq);
+			return;
+		}
+		// we bring the list of AVPs of the transaction to the current context
+		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from);
+		set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to);
+		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from);
+		set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to);
+		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from);
+		set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to);
+
+		if (t)
+			tmb.unref_cell(t);
+
+		LM_DBG("resuming transaction (%d:%d)\n", tindex, tlabel);
+
+		if(act!=NULL)
+			tmb.t_continue(tindex, tlabel, act);
+	} else {
+		fmsg = faked_msg_next();
+		if (run_top_route(act, fmsg, 0)<0)
+			LM_ERR("failure inside run_top_route\n");
+	}
+
+	free_sip_msg(ah_reply);
+	free_async_query(aq);
+
+	return;
+}
+
+void notification_socket_cb(int fd, short event, void *arg)
+{
+	(void)fd; /* unused */
+	(void)event; /* unused */
+	const async_http_worker_t *worker = (async_http_worker_t *) arg;
+
+	int received;
+	int i;
+	async_query_t *aq;
+
+	http_m_params_t query_params;
+
+	str query;
+	str post;
+
+	if ((received = recvfrom(worker->notication_socket[0],
+			&aq, sizeof(async_query_t*),
+			0, NULL, 0)) < 0) {
+		LM_ERR("failed to read from socket (%d: %s)\n", errno, strerror(errno));
+		return;
+	}
+
+	if(received != sizeof(async_query_t*)) {
+		LM_ERR("invalid query size %d\n", received);
+		return;
+	}
+
+	query = ((str)aq->query);
+	post = ((str)aq->post);
+
+	query_params.timeout = aq->query_params.timeout;
+	query_params.verify_peer = aq->query_params.verify_peer;
+	query_params.verify_host = aq->query_params.verify_host;
+	query_params.headers = NULL;
+	for (i = 0 ; i < aq->query_params.headers.len ; i++) {
+		query_params.headers = curl_slist_append(query_params.headers, aq->query_params.headers.t[i]);
+	}
+	query_params.method  = aq->query_params.method;
+
+	query_params.ssl_cert.s = NULL;
+	query_params.ssl_cert.len = 0;
+	if (aq->query_params.ssl_cert.s && aq->query_params.ssl_cert.len > 0) {
+		if (shm_str_dup(&query_params.ssl_cert, &(aq->query_params.ssl_cert)) < 0) {
+			LM_ERR("Error allocating query_params.ssl_cert\n");
+			return;
+		}
+	}
+
+	query_params.ssl_key.s = NULL;
+	query_params.ssl_key.len = 0;
+	if (aq->query_params.ssl_key.s && aq->query_params.ssl_key.len > 0) {
+		if (shm_str_dup(&query_params.ssl_key, &(aq->query_params.ssl_key)) < 0) {
+			LM_ERR("Error allocating query_params.ssl_key\n");
+			return;
+		}
+	}
+
+	query_params.ca_path.s = NULL;
+	query_params.ca_path.len = 0;
+	if (aq->query_params.ca_path.s && aq->query_params.ca_path.len > 0) {
+		if (shm_str_dup(&query_params.ca_path, &(aq->query_params.ca_path)) < 0) {
+			LM_ERR("Error allocating query_params.ca_path\n");
+			return;
+		}
+	}
+
+	LM_DBG("query received: [%.*s] (%p)\n", query.len, query.s, aq);
+
+	if (new_request(&query, &post, &query_params, async_http_cb, aq) < 0) {
+		LM_ERR("Cannot create request for %.*s\n", query.len, query.s);
+		free_async_query(aq);
+	}
+
+	if (query_params.ssl_cert.s && query_params.ssl_cert.len > 0) {
+		shm_free(query_params.ssl_cert.s);
+		query_params.ssl_cert.s = NULL;
+		query_params.ssl_cert.len = 0;
+	}
+	if (query_params.ssl_key.s && query_params.ssl_key.len > 0) {
+		shm_free(query_params.ssl_key.s);
+		query_params.ssl_key.s = NULL;
+		query_params.ssl_key.len = 0;
+	}
+	if (query_params.ca_path.s && query_params.ca_path.len > 0) {
+		shm_free(query_params.ca_path.s);
+		query_params.ca_path.s = NULL;
+		query_params.ca_path.len = 0;
+	}
+
+	return;
+}
+
+int init_socket(async_http_worker_t *worker)
+{
+	worker->socket_event = event_new(worker->evbase, worker->notication_socket[0], EV_READ|EV_PERSIST, notification_socket_cb, worker);
+	event_add(worker->socket_event, NULL);
+	return (0);
+}
+
+int async_send_query(sip_msg_t *msg, str *query, str *post, cfg_action_t *act)
+{
+	async_query_t *aq;
+	unsigned int tindex = 0;
+	unsigned int tlabel = 0;
+	short suspend = 0;
+	int dsize;
+	tm_cell_t *t = 0;
+
+	if(query==0) {
+		LM_ERR("invalid parameters\n");
+		return -1;
+	}
+
+	t = tmb.t_gett();
+	if (t==NULL || t==T_UNDEFINED) {
+		LM_DBG("no pre-existing transaction, switching to transaction-less behavior\n");
+	} else if (!ah_params.suspend_transaction) {
+		LM_DBG("transaction won't be suspended\n");
+	} else {
+		if(tmb.t_suspend==NULL) {
+			LM_ERR("http async query is disabled - tm module not loaded\n");
+			return -1;
+		}
+
+		if(tmb.t_suspend(msg, &tindex, &tlabel)<0) {
+			LM_ERR("failed to suspend request processing\n");
+			return -1;
+		}
+
+		suspend = 1;
+
+		LM_DBG("transaction suspended [%u:%u]\n", tindex, tlabel);
+	}
+	dsize = sizeof(async_query_t);
+	aq = (async_query_t*)shm_malloc(dsize);
+
+	if(aq==NULL)
+	{
+		LM_ERR("no more shm\n");
+		goto error;
+	}
+	memset(aq,0,dsize);
+
+    if(shm_str_dup(&aq->query, query)<0) {
+		goto error;
+	}
+
+	if (post != NULL) {
+
+		if(shm_str_dup(&aq->post, post)<0) {
+			goto error;
+		}
+	}
+
+	aq->param = act;
+	aq->tindex = tindex;
+	aq->tlabel = tlabel;
+	
+	aq->query_params.verify_peer = ah_params.verify_peer;
+	aq->query_params.verify_host = ah_params.verify_host;
+	aq->query_params.suspend_transaction = suspend;
+	aq->query_params.timeout = ah_params.timeout;
+	aq->query_params.headers = ah_params.headers;
+	aq->query_params.method = ah_params.method;
+
+	aq->query_params.ssl_cert.s = NULL;
+	aq->query_params.ssl_cert.len = 0;
+	if (ah_params.ssl_cert.s && ah_params.ssl_cert.len > 0) {
+		if (shm_str_dup(&aq->query_params.ssl_cert, &(ah_params.ssl_cert)) < 0) {
+			LM_ERR("Error allocating aq->query_params.ssl_cert\n");
+			goto error;
+		}
+	}
+
+	aq->query_params.ssl_key.s = NULL;
+	aq->query_params.ssl_key.len = 0;
+	if (ah_params.ssl_key.s && ah_params.ssl_key.len > 0) {
+		if (shm_str_dup(&aq->query_params.ssl_key, &(ah_params.ssl_key)) < 0) {
+			LM_ERR("Error allocating aq->query_params.ssl_key\n");
+			goto error;
+		}
+	}
+
+	aq->query_params.ca_path.s = NULL;
+	aq->query_params.ca_path.len = 0;
+	if (ah_params.ca_path.s && ah_params.ca_path.len > 0) {
+		if (shm_str_dup(&aq->query_params.ca_path, &(ah_params.ca_path)) < 0) {
+			LM_ERR("Error allocating aq->query_params.ca_path\n");
+			goto error;
+		}
+	}
+
+	set_query_params(&ah_params);
+
+	if(async_push_query(aq)<0) {
+		LM_ERR("failed to relay query: %.*s\n", query->len, query->s);
+		goto error;
+	}
+
+	if (suspend) 
+		/* force exit in config */
+		return 0;
+	
+	/* continue route processing */
+	return 1;
+
+error:
+
+	if (suspend)
+		tmb.t_cancel_suspend(tindex, tlabel);
+	free_async_query(aq);
+	return -1;
+}
+
+int async_push_query(async_query_t *aq)
+{
+	int len;
+	int worker;
+	static unsigned long rr = 0; /* round robin */
+
+	str query;
+
+	query = ((str)aq->query);
+
+	worker = rr++ % num_workers;
+	len = write(workers[worker].notication_socket[1], &aq, sizeof(async_query_t*));
+	if(len<=0) {
+		LM_ERR("failed to pass the query to async workers\n");
+		return -1;
+	}
+	LM_DBG("query sent [%.*s] (%p) to worker %d\n", query.len, query.s, aq, worker + 1);
+	return 0;
+}
+
+void init_query_params(struct query_params *p) {
+	memset(&ah_params, 0, sizeof(struct query_params));
+	set_query_params(p);
+}
+
+void set_query_params(struct query_params *p) {
+	p->headers.len = 0;
+	p->headers.t = NULL;
+	p->verify_host = verify_host;
+	p->verify_peer = verify_peer;
+	p->suspend_transaction = 1;
+	p->timeout = http_timeout;
+	p->method = AH_METH_DEFAULT;
+
+	if (p->ssl_cert.s && p->ssl_cert.len > 0) {
+		shm_free(p->ssl_cert.s);
+		p->ssl_cert.s = NULL;
+		p->ssl_cert.len = 0;
+	}
+	if (ssl_cert.s && ssl_cert.len > 0) {
+		if (shm_str_dup(&p->ssl_cert, &ssl_cert) < 0) {
+			LM_ERR("Error allocating ssl_cert\n");
+			return;
+		}
+	}
+
+	if (p->ssl_key.s && p->ssl_key.len > 0) {
+		shm_free(p->ssl_key.s);
+		p->ssl_key.s = NULL;
+		p->ssl_key.len = 0;
+	}
+	if (ssl_key.s && ssl_key.len > 0) {
+		if (shm_str_dup(&p->ssl_key, &ssl_key) < 0) {
+			LM_ERR("Error allocating ssl_key\n");
+			return;
+		}
+	}
+
+	if (p->ca_path.s && p->ca_path.len > 0) {
+		shm_free(p->ca_path.s);
+		p->ca_path.s = NULL;
+		p->ca_path.len = 0;
+	}
+	if (ca_path.s && ca_path.len > 0) {
+		if (shm_str_dup(&p->ca_path, &ca_path) < 0) {
+			LM_ERR("Error allocating ca_path\n");
+			return;
+		}
+	}
+}

+ 157 - 0
modules/http_async_client/async_http.h

@@ -0,0 +1,157 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _ASYNC_HTTP_
+#define _ASYNC_HTTP_
+
+#define RC_AVP_NAME "http_rc"
+#define RC_AVP_NAME_LENGTH 7
+#define RB_AVP_NAME "http_rb"
+#define RB_AVP_NAME_LENGTH 7
+#define ERROR_AVP_NAME "http_error"
+#define ERROR_AVP_NAME_LENGTH 10
+
+#include <curl/curl.h>
+#include <event2/event.h>
+
+#include "../../pvar.h"
+
+#include "http_multi.h"
+
+extern int num_workers;
+
+extern int http_timeout; /* query timeout in ms */
+
+extern struct sip_msg *ah_reply;
+extern str ah_error;
+
+extern int verify_host;
+extern int verify_peer;
+extern str ssl_cert;
+extern str ssl_key;
+extern str ca_path;
+
+
+typedef struct async_http_worker {
+	int notication_socket[2];
+	struct event_base *evbase;
+	struct event *socket_event;
+	struct http_m_global *g;
+} async_http_worker_t;
+
+extern async_http_worker_t *workers;
+
+typedef enum {
+	AH_METH_DEFAULT = 0,
+	AH_METH_GET, AH_METH_POST, AH_METH_PUT, AH_METH_DELETE
+} async_http_method_t;
+
+struct header_list {
+	char ** t;
+	int len;
+};
+
+struct query_params {
+	async_http_method_t method:3;
+	unsigned int verify_peer:1;
+	unsigned int verify_host:1;
+	unsigned int suspend_transaction:1; /* (create and) suspend the current transaction */
+	unsigned int call_route:1;          /* call script route on reply */
+
+	unsigned int timeout;
+	struct header_list headers;
+	str ssl_cert;
+	str ssl_key;
+	str ca_path;
+};
+
+extern struct query_params ah_params;
+
+typedef struct async_query {
+	str query;
+	str post;
+	unsigned int tindex;
+	unsigned int tlabel;
+	struct query_params query_params;
+	void *param;
+} async_query_t;
+
+int async_http_init_sockets(async_http_worker_t *worker);
+int async_http_init_worker(int prank, async_http_worker_t* worker);
+void async_http_run_worker(async_http_worker_t* worker);
+
+int async_send_query(sip_msg_t *msg, str *query, str *post, cfg_action_t *act);
+int async_push_query(async_query_t *aq);
+
+void notification_socket_cb(int fd, short event, void *arg);
+int init_socket(async_http_worker_t* worker);
+void async_http_cb(struct http_m_reply *reply, void *param);
+void init_query_params(struct query_params*);
+void set_query_params(struct query_params*);
+
+static inline void free_async_query(async_query_t *aq)
+{
+	if (!aq)
+		return;
+	LM_DBG("freeing query %p\n", aq);
+	if (aq->query.s && aq->query.len) {
+		shm_free(aq->query.s);
+		aq->query.s=0;
+		aq->query.len=0;
+	}
+
+	if (aq->post.s && aq->post.len) {
+		shm_free(aq->post.s);
+		aq->post.s=0;
+		aq->post.len=0;
+	}
+
+	if(aq->query_params.headers.t) {
+		while(aq->query_params.headers.len--)
+			shm_free(aq->query_params.headers.t[aq->query_params.headers.len]);
+		shm_free(aq->query_params.headers.t);
+	}
+
+	if (aq->query_params.ssl_cert.s && aq->query_params.ssl_cert.len > 0) {
+		shm_free(aq->query_params.ssl_cert.s);
+		aq->query_params.ssl_cert.s = NULL;
+		aq->query_params.ssl_cert.len = 0;
+	}
+
+	if (aq->query_params.ssl_key.s && aq->query_params.ssl_key.len > 0) {
+		shm_free(aq->query_params.ssl_key.s);
+		aq->query_params.ssl_key.s = NULL;
+		aq->query_params.ssl_key.len = 0;
+	}
+
+	if (aq->query_params.ca_path.s && aq->query_params.ca_path.len > 0) {
+		shm_free(aq->query_params.ca_path.s);
+		aq->query_params.ca_path.s = NULL;
+		aq->query_params.ca_path.len = 0;
+	}
+
+	shm_free(aq);
+}
+
+#endif

+ 4 - 0
modules/http_async_client/doc/Makefile

@@ -0,0 +1,4 @@
+docs = http_async_client.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 51 - 0
modules/http_async_client/doc/http_async_client.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>ASYNC_HTTP Module</title>
+	<productname class="trade">kamailio.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Federico</firstname>
+		<surname>Cabiddu</surname>
+		<email>[email protected]</email>
+	    </author>
+
+	    <author>
+		<firstname>Giacomo</firstname>
+		<surname>Vacca</surname>
+		<email>[email protected]</email>
+	    </author>
+
+	    <author>
+		<firstname>Camille</firstname>
+		<surname>Oudot</surname>
+        <affiliation><orgname>Orange</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+
+	    <editor>
+		<firstname>Federico</firstname>
+		<surname>Cabiddu</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2016</year>
+	    <holder>Federico Cabiddu</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="http_async_client_admin.xml"/>
+    
+    
+</book>

+ 620 - 0
modules/http_async_client/doc/http_async_client_admin.xml

@@ -0,0 +1,620 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This module performs asynchronous HTTP queries.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>tm</emphasis> - Transaction module
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+				<emphasis>pv</emphasis> - Pseudo-Variables module
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+            <para>
+				<emphasis>libcurl</emphasis>
+				<emphasis>libev</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section>
+		<title><varname>workers</varname> (int)</title>
+		<para>
+            Number of worker processes to be started to send HTTP requests
+            and asynchronously handle responses.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>workers</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "workers", 2)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>connection_timeout</varname> (str)</title>
+        <para>
+            Defines in milliseconds how long &kamailio; waits for response
+            from HTTP server.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 500ms.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>connection_timeout</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "connection_timeout", 1000)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>hash_size</varname> (str)</title>
+        <para>
+		    The size of the hash table internally used to keep the requests. A
+		    larger table is much faster but consumes more memory. The hash size
+		    must be a power of two, otherwise it will be rounded down to the nearest
+		    power of two.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 2048.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>hash_size</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "hash_size", 1024)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>tlsversion</varname> (int)</title>
+        <para>
+		    For HTTPS connections, what's the preferred SSL version.
+		    http://curl.haxx.se/libcurl/c/CURLOPT_SSLVERSION.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0 (default SSL version).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlsversion</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlsversion", 6)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>tlsverifyhost</varname> (int)</title>
+        <para>
+		    For HTTPS connections, whether the client should verify the server host.
+		    http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is 2 (enabled).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlsverifyhost</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlsverifyhost", 0)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>tlsverifypeer</varname> (int)</title>
+        <para>
+		    For HTTPS connections, whether the client should verify the server identity.
+		    http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1 (enabled).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlsverifypeer</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlsverifypeer", 0)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>curl_verbose</varname> (int)</title>
+        <para>
+		    If defined to a non-zero value, extra informations from cURL (request and response headers)
+		    will be included in the kamailio logs, with LM_INFO priority.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0 (disabled).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>curl_verbose</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "curl_verbose", 1)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>memory_manager</varname> (string)</title>
+        <para>
+			Choose the memory manager used by curl:
+			<itemizedlist>
+				<listitem><para>
+					<emphasis>shm</emphasis>: curl will use kamailio's SHM pool and memory manager
+				</para></listitem>
+				<listitem><para>
+					<emphasis>sys</emphasis>: curl will use the system memory amd memory manager (malloc, free, ...)
+				</para></listitem>
+			</itemizedlist>
+			<para>
+				<emphasis>Note:</emphasis> if this module is used in conjunction with another module using libcurl, this parameter will have no effect, and curl will likely use the system memory allocator by default.
+			</para>
+		</para>
+		<para>
+		<emphasis>
+			Default value "shm"
+		</emphasis>
+		</para>
+		<example>
+			<title>Set <varname>memory_manager</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "memory_manager", "sys")
+...
+</programlisting>
+              </example>
+      </section>
+	<section>
+		<title><varname>tlsclientcert</varname> (int)</title>
+        <para>
+		    For HTTPS connections, the file path of the TLS client certificate to be used
+		    http://curl.haxx.se/libcurl/c/CURLOPT_SSLCERT.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is NULL (not used). Default type is PEM.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlsclientcert</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlsclientcert", "/etc/kamailio/ssl/clientcert.pem")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>tlsclientkey</varname> (int)</title>
+        <para>
+		    For HTTPS connections, the file path of the TLS client certificate key
+		    http://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is NULL (not used).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlsclientkey</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlsclientkey", "/etc/kamailio/ssl/clientcert.key")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>tlscapath</varname> (int)</title>
+        <para>
+		    The path of the directory holding the CA certificates.
+                    http://curl.haxx.se/libcurl/c/CURLOPT_CAPATH.html
+		</para>
+		<para>
+		<emphasis>
+			Default value is NULL..
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tlscapath</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async_http", "tlscapath", "/etc/kamailio/ssl/ca/")
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section id="async_http.f.http_async_query">
+	    <title>
+		<function moreinfo="none">http_async_query(url, [post_data], route_name)</function>
+	    </title>
+        <para>
+            Sends HTTP(S) request asyncronously to the URL given in <quote>url</quote> parameter, which is a string that may contain pseudo variables.
+        </para>
+        <para>
+            Unless a specific HTTP method was specified using <emphasis>http_async_set_method()</emphasis>, it defaults to a GET request, or to a POST request if <quote>post_data</quote> is issued as second argument.
+        </para>
+        <para>
+            Parameter <quote>post_data</quote>, optional, which is sent as the body of the request, may also contain pseudo variables.
+        </para>
+        <para>
+			Parameter <quote>route_name</quote> defines the route to be executed upon reception of HTTP reply, on error or on timeout. 
+			If a transaction exists before calling <emphasis>http_async_query()</emphasis>, it will be paused and resumed in this route, while the routing script execution will be stopped.
+			If executed in a transactionless context, or if <emphasis>http_async_suspend_transaction()</emphasis> is used to not suspend the transaction, the routing script execution will 
+			continue and the query result will be available in <quote>route_name</quote>.
+        </para>
+        <para>
+            Return value: 0 (stop script execution) on success in transaction context, 1 (continue script execution) in transaction-less context or if http_async_suspend_transaction(0) is used, -1 on error.
+        </para>
+        <para>
+		    This function can be used from ANY_ROUTE.
+        </para>
+        <para>
+            This method is executed asynchronously. The HTTP return code, body and error are returned in the module-specific $http_* PVs (see below).
+            See example on how to retrieve return values.
+        </para>
+		<example>
+		<title><function>http_async_query()</function> usage</title>
+		<programlisting format="linespecific">
+...
+# create a transaction to be paused, and resumed in route[HTTP_REPLY]
+t_newtran();
+# GET
+http_async_query("http://example.com/test.php?r_uri=$rU&amp;f_uri=$fU", "HTTP_REPLY");
+...
+# POST
+http_async_query("http://example.com/test.php", "{'r_uri':'$rU', 'f_uri':'$fU'}", "HTTP_REPLY");
+...
+route[HTTP_REPLY] {
+    if ($http_ok) {
+        xlog("L_INFO", "route[HTTP_REPLY]: status $http_rs\n");
+        xlog("L_INFO", "route[HTTP_REPLY]: body   $http_rb\n");
+    } else {
+        xlog("L_INFO", "route[HTTP_REPLY]: error  $http_error)\n");
+    }
+}
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_async_suspend">
+	    <title>
+		<function moreinfo="none">http_async_suspend(suspend)</function>
+	    </title>
+        <para>
+		    In a transaction context if the transaction must be suspended and script execution stopped.
+        </para>
+        <para>
+            Parameter <quote>suspend</quote> set to "1" to suspend the transaction, "0" to not suspend and continue with script execution. Default: 1 (transaction suspended).
+        </para>
+		<example>
+		<title><function>http_async_suspend()</function> usage</title>
+		<programlisting format="linespecific">
+...
+t_newtran();
+http_async_suspend(0);
+# the transaction won't be suspended for the next query
+http_async_query("http://example.com/test.php", "HTTP_REPLY");
+xlog("L_INFO", "query sent\n");
+t_reply("200", "Ok");
+
+...
+        </programlisting>
+	    </example>
+	</section>
+	<section id="async_http.f.http_verify_host">
+	    <title>
+		<function moreinfo="none">http_verify_host(verify)</function>
+	    </title>
+        <para>
+		    For the next HTTPS connection, whether the client should verify the server host.
+        </para>
+        <para>
+            Parameter <quote>verify</quote> set to "1" to enable the host verification, "0" to disable. Default: the global value set as <emphasis>verify_host</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_verify_host()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_verify_host("0");
+# host verification is disabled for the next query
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_verify_peer">
+	    <title>
+		<function moreinfo="none">http_verify_peer(verify)</function>
+	    </title>
+        <para>
+		    For the next HTTPS connection, whether the client should verify the server identity.
+        </para>
+        <para>
+            Parameter <quote>verify</quote> set to "1" to enable the identity verification, "0" to disable. Default: the global value set as <emphasis>verify_peer</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_verify_peer()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_verify_peer("0");
+# server identity verification is disabled for the next query
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_set_timeout">
+	    <title>
+		<function moreinfo="none">http_set_timeout(timeout)</function>
+	    </title>
+        <para>
+		    For the next HTTP query, set the response timeout.
+        </para>
+        <para>
+            Parameter <quote>timeout</quote> string representing the timeout in milliseconds. Default: the global value set as <emphasis>http_timeout</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_set_timeout()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_set_timeout("200");
+# the server must respond in maximum 200ms, otherwise the query will fail
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_append_header">
+	    <title>
+		<function moreinfo="none">http_append_header(header)</function>
+	    </title>
+        <para>
+		    Set/remove/replace a header in the next HTTP query.
+        </para>
+        <para>From libcurl's documentation:</para>
+        <para>
+			<quote>If you add a header that is otherwise generated and used by libcurl internally, your added one will be used instead. If you add a header with no content as in 'Accept:' (no data on the right side of the colon), the internally used header will get disabled. With this option you can add new headers, replace internal headers and remove internal headers. To add a header with no content (nothing to the right side of the colon), use the form 'MyHeader;' (note the ending semicolon).</quote>
+        </para>
+        <para>
+			<quote>The headers included in the linked list <emphasis>must not</emphasis> be CRLF-terminated, because libcurl adds CRLF after each header item.</quote>
+		</para>
+        <para>
+            Parameter <quote>header</quote> string representing the header to pass to libcurl for the next query.
+        </para>
+		<example>
+		<title><function>http_append_header()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_append_header("X-Sip-Call-Id: $ci");
+# a new 'X-Sip-Call-Id' header will be added to the next query
+http_append_header("Content-Type": application/json");
+# the curl default 'application/x-www-form-urlencoded' Content-Type will be replaced
+http_query("https://example.com/test.php", "{'foo': 'bar'}", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_set_method">
+	    <title>
+		<function moreinfo="none">http_set_method(method)</function>
+	    </title>
+        <para>
+		    For the next HTTP query, set the method.
+        </para>
+        <para>
+            Parameter <quote>method</quote> string representing the method (verb): either "GET", "POST", "PUT" or "DELETE" (these are the only supported methods). (Note: if http_set_method() is not called before a query, curl will use GET, or POST if a body is specified)
+        </para>
+		<example>
+		<title><function>http_set_method()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_set_method("PUT");
+# the next query will be a HTTP PUT request
+http_query("https://example.com/test.php", "{'foo': 'bar'}", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_set_ssl_cert">
+	    <title>
+		<function moreinfo="none">http_set_ssl_cert(path)</function>
+	    </title>
+        <para>
+		    For the next HTTPS connection, what client certificate to use.
+        </para>
+        <para>
+            Default: the global value set as <emphasis>ssl_cert</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_set_ssl_cert()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_set_ssl_cert("/etc/kamailio/ssl/cert.pem");
+# Next query will use the client cert above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_set_ssl_key">
+	    <title>
+		<function moreinfo="none">http_set_ssl_key(path)</function>
+	    </title>
+        <para>
+		    For the next HTTPS connection, what client certificate key to use.
+        </para>
+        <para>
+            Default: the global value set as <emphasis>ssl_key</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_set_ssl_key()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_set_ssl_key("/etc/kamailio/ssl/cert.key");
+# Next query will use the client cert key above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+	<section id="async_http.f.http_set_ca_path">
+	    <title>
+		<function moreinfo="none">http_set_ca_path(path)</function>
+	    </title>
+        <para>
+		    For the next HTTP connection, what CA certificate files to use.
+        </para>
+        <para>
+            Default: the global value set as <emphasis>ca_path</emphasis> module parameter.
+        </para>
+		<example>
+		<title><function>http_set_ca_path()</function> usage</title>
+		<programlisting format="linespecific">
+...
+http_set_ssl_key("/etc/kamailio/ssl/ca/");
+# Next query will use the CA certs above
+http_query("https://example.com/test.php", "HTTP_REPLY");
+...
+        </programlisting>
+	    </example>
+	</section>
+
+</section>
+
+<section>
+	<title>Pseudo Variables</title>
+	<para>The following pseudo variables can only be used in the callback routes executed by http_async_query()</para>
+	<itemizedlist>
+		<listitem><para>
+			<emphasis>$http_ok</emphasis>: 1 if cURL executed the request successfully, 0 otherwise (check $ah_err for details)
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_err</emphasis>: cURL error string if an error occured, $null otherwise
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_rs</emphasis>: http status
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_rr</emphasis>: http reason phrase
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_hdr(Name)</emphasis>: value of the <emphasis>Name</emphasis> header (the <emphasis>$(ah_hdr(Name)[N])</emphasis> syntax can also be used, check the SIP $hdr() PV documentation for details) 
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_mb</emphasis> and <emphasis>$http_ml</emphasis>: HTTP response buffer (including headers) and length 
+		</para></listitem>
+		<listitem><para>
+			<emphasis>$http_rb</emphasis> and <emphasis>$http_bs</emphasis>: HTTP response body and body length
+		</para></listitem>
+	</itemizedlist>
+</section>
+    
+<section>
+	<title>Statistics</title>
+	<section>
+		<title><varname>requests</varname></title>
+		<para>
+		The number of http requests sent.
+		</para>
+	</section>
+	<section>
+		<title><varname>replies</varname></title>
+		<para>
+		The number of received http replies.
+		</para>
+	</section>
+	<section>
+		<title><varname>errors</varname></title>
+		<para>
+		The number of errors.
+		</para>
+	</section>
+	<section>
+		<title><varname>timeouts</varname></title>
+		<para>
+		The number of timed out requests.
+		</para>
+	</section>
+
+</section>
+</chapter>
+

+ 186 - 0
modules/http_async_client/hm_hash.c

@@ -0,0 +1,186 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "hm_hash.h"
+
+extern int hash_size;
+
+/*!
+ * \brief Initialize the global http multi table
+ * \param size size of the table
+ * \return 0 on success, -1 on failure
+ */
+int init_http_m_table(unsigned int size)
+{
+	unsigned int i;
+
+	hm_table = (struct http_m_table*)shm_malloc
+		( sizeof(struct http_m_table) + size*sizeof(struct http_m_entry) );
+	if (hm_table==0) {
+		LM_ERR("no more shm mem\n");
+		return -1;
+	}
+
+	memset( hm_table, 0, sizeof(struct http_m_table) );
+	hm_table->size = size;
+	hm_table->entries = (struct http_m_entry*)(hm_table+1);
+
+	for( i=0 ; i<size; i++ ) {
+		memset( &(hm_table->entries[i]), 0, sizeof(struct http_m_entry) );
+	}
+
+	LM_DBG("hash table %p initialized with size %d\n", hm_table, size);
+	return 0;
+}
+unsigned int build_hash_key(void *p)
+{
+	str			*hash_str;
+	char		*pointer_str;
+	int			len;
+
+	unsigned int hash;
+
+	pointer_str = (char *)pkg_malloc(sizeof(p) + 1);
+
+	if (pointer_str==0) {
+		LM_ERR("no more pkg mem\n");
+		return 0;
+	}
+
+	sprintf(pointer_str, "%p", p);
+	len = strlen(pointer_str);
+	LM_DBG("received id %p (%d)-> %s (%d)\n", p, (int)sizeof(p), pointer_str, len);
+
+	hash_str = (str *)pkg_malloc(sizeof(str));
+	if (hash_str==0) {
+		LM_ERR("no more pkg mem\n");
+		pkg_free(pointer_str);
+		return 0;
+	}
+	hash_str->s = pointer_str;
+	hash_str->len = len;
+
+	hash = core_hash(hash_str, 0, hash_size);
+
+	LM_DBG("hash for %p is %d\n", p, hash);
+
+	pkg_free(pointer_str);
+	pkg_free(hash_str);
+
+	return hash;
+
+}
+
+struct http_m_cell* build_http_m_cell(void *p)
+{
+	struct http_m_cell *cell= NULL;
+	int len;
+
+	len = sizeof(struct http_m_cell);
+	cell = (struct http_m_cell*)shm_malloc(len);
+	if (cell==0) {
+		LM_ERR("no more shm mem\n");
+		return 0;
+	}
+
+	memset( cell, 0, len);
+
+	cell->hmt_entry = build_hash_key(p);
+	cell->easy = p;
+
+	LM_DBG("hash id for %p is %d\n", p, cell->hmt_entry);
+
+	return cell;
+}
+
+void link_http_m_cell(struct http_m_cell *cell)
+{
+	struct http_m_entry *hmt_entry;
+
+	hmt_entry = &(hm_table->entries[cell->hmt_entry]);
+
+	LM_DBG("linking new cell %p to table %p [%u]\n", cell, hm_table, cell->hmt_entry);
+	if (hmt_entry->first==0) {
+		hmt_entry->first = cell;
+		hmt_entry->first = hmt_entry->last = cell;
+	}
+	else {
+		hmt_entry->last->next = cell;
+		cell->prev = hmt_entry->last;
+		hmt_entry->last = cell;
+	}
+
+	return;
+}
+
+struct http_m_cell *http_m_cell_lookup(CURL *p)
+{
+	struct http_m_entry	*hmt_entry;
+	struct http_m_cell	*current_cell;
+
+	unsigned int		entry_idx;
+
+	entry_idx = build_hash_key(p);
+
+	hmt_entry = &(hm_table->entries[entry_idx]);
+
+	for (current_cell = hmt_entry->first; current_cell; current_cell = current_cell->next) {
+		if (current_cell->easy == p) {
+			LM_DBG("http_m_cell with easy=%p found on table entry %u\n\n", p, entry_idx);
+			return current_cell;
+		}
+	}
+
+	/* not found */
+	LM_DBG("No http_m_cell with easy=%p found on table entry %u", p, entry_idx);
+	return 0;
+}
+
+void free_http_m_cell(struct http_m_cell *cell)
+{
+	if (!cell) return;
+
+	if(cell->params.headers) {
+		if(cell->params.headers) curl_slist_free_all(cell->params.headers);
+	}
+
+	if (cell->reply) {
+		if (cell->reply->result) {
+			if (cell->reply->result->s) {
+				shm_free(cell->reply->result->s);
+			}
+			shm_free(cell->reply->result);
+		}
+		shm_free(cell->reply);
+	}
+
+	if (cell->url) shm_free(cell->url);
+
+	if (cell->post_data) {
+		shm_free(cell->post_data);
+	}
+
+	shm_free(cell);
+}
+

+ 137 - 0
modules/http_async_client/hm_hash.h

@@ -0,0 +1,137 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _HM_HASH_
+#define _HM_HASH_
+
+#include <curl/curl.h>
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../ut.h"
+#include "../../hashes.h"
+
+extern struct http_m_table *hm_table;
+
+typedef struct http_m_reply
+{
+	long retcode;
+	str *result;
+	char error[CURL_ERROR_SIZE];
+} http_m_reply_t;
+
+typedef void (*http_multi_cbe_t)(struct http_m_reply *reply, void *param);
+
+typedef struct http_m_request
+{
+	http_multi_cbe_t cb;
+	char *url;
+	str *result;
+	void *param;
+} http_m_request_t;
+
+typedef struct http_m_global
+{
+	struct event_base *evbase;
+	struct event *timer_event;
+	CURLM *multi;
+	int still_running;
+} http_m_global_t;
+
+typedef struct hm_params {
+	int timeout;
+	int verify_host;
+	int verify_peer;
+	struct curl_slist* headers;
+	int method;
+	str ssl_cert;
+	str ssl_key;
+	str ca_path;
+} http_m_params_t;
+
+typedef struct http_m_cell
+{
+	struct http_m_cell	*next;
+	struct http_m_cell	*prev;
+	//unsigned int 		hmt_id;
+	unsigned int 		hmt_entry;
+
+	struct http_m_global *global;
+
+	CURL *easy;
+	curl_socket_t sockfd;
+	int action;
+	http_m_params_t params;
+
+	struct event *ev;
+	int evset;
+
+	char *url;
+	char *post_data;
+	char error[CURL_ERROR_SIZE];
+
+	http_multi_cbe_t cb;
+	void *param;
+
+	struct http_m_reply *reply;
+} http_m_cell_t;
+
+typedef struct http_m_entry
+{
+	struct http_m_cell 	*first;
+	struct http_m_cell 	*last;
+} http_m_entry_t;
+
+/*! main http multi table */
+typedef struct http_m_table
+{
+	unsigned int 		size;
+	struct http_m_entry	*entries;
+} http_m_table_t;
+
+int init_http_m_table(unsigned int size);
+struct http_m_cell* build_http_m_cell(void *p);
+void link_http_m_cell(struct http_m_cell *cell);
+struct http_m_cell *http_m_cell_lookup(CURL *p);
+void free_http_m_cell(struct http_m_cell *cell);
+
+static inline void unlink_http_m_cell(struct http_m_cell *hmt_cell)
+{
+	struct http_m_entry	*hmt_entry;
+
+	if (hmt_cell) {
+		hmt_entry = &(hm_table->entries[hmt_cell->hmt_entry]);
+		if (hmt_cell->next)
+			hmt_cell->next->prev = hmt_cell->prev;
+		else
+			hmt_entry->last = hmt_cell->prev;
+		if (hmt_cell->prev)
+			hmt_cell->prev->next = hmt_cell->next;
+		else
+			hmt_entry->first = hmt_cell->next;
+
+		hmt_cell->next = hmt_cell->prev = 0;
+	}
+	return;
+}
+#endif

+ 735 - 0
modules/http_async_client/http_async_client_mod.c

@@ -0,0 +1,735 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../globals.h"
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../pt.h"
+#include "../../pvar.h"
+#include "../../mem/shm_mem.h"
+#include "../../mod_fix.h"
+#include "../../pvar.h"
+#include "../../cfg/cfg_struct.h"
+#include "../../lib/kcore/faked_msg.h"
+
+#include "../../modules/tm/tm_load.h"
+#include "../../modules/pv/pv_api.h"
+
+#include "async_http.h"
+
+MODULE_VERSION
+
+extern int  num_workers;
+
+int http_timeout = 500; /* query timeout in ms */
+int hash_size = 2048;
+int ssl_version = 0; // Use default SSL version in HTTPS requests (see curl/curl.h)
+int verify_host = 1; // By default verify host in HTTPS requests
+int verify_peer = 1; // By default verify peer in HTTPS requests
+int curl_verbose = 0;
+str ssl_cert = STR_STATIC_INIT(""); // client SSL certificate path, defaults to NULL
+str ssl_key = STR_STATIC_INIT(""); // client SSL certificate key path, defaults to NULL
+str ca_path = STR_STATIC_INIT(""); // certificate authority dir path, defaults to NULL
+static char *memory_manager = "shm";
+extern int curl_memory_manager;
+
+static int  mod_init(void);
+static int  child_init(int);
+static void mod_destroy(void);
+
+static int w_http_async_get(sip_msg_t* msg, char* query, char* rt);
+static int w_http_async_post(sip_msg_t* msg, char* query, char* post, char* rt);
+static int w_http_verify_host(sip_msg_t* msg, char* vh, char*);
+static int w_http_verify_peer(sip_msg_t* msg, char* vp, char*);
+static int w_http_async_suspend_transaction(sip_msg_t* msg, char* vp, char*);
+static int w_http_set_timeout(sip_msg_t* msg, char* tout, char*);
+static int w_http_append_header(sip_msg_t* msg, char* hdr, char*);
+static int w_http_set_method(sip_msg_t* msg, char* method, char*);
+static int w_http_set_ssl_cert(sip_msg_t* msg, char* sc, char*);
+static int w_http_set_ssl_key(sip_msg_t* msg, char* sk, char*);
+static int w_http_set_ca_path(sip_msg_t* msg, char* cp, char*);
+static int fixup_http_async_get(void** param, int param_no);
+static int fixup_http_async_post(void** param, int param_no);
+
+/* pv api binding */
+static int ah_get_reason(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_hdr(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int w_pv_parse_hdr_name(pv_spec_p sp, str *in);
+static int ah_get_status(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_msg_body(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_body_size(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_msg_buf(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_msg_len(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+
+static str pv_str_1 = {"1", 1};
+static str pv_str_0 = {"0", 1};
+
+static int ah_get_ok(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+static int ah_get_err(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+
+/* tm */
+struct tm_binds tmb;
+/* pv */
+pv_api_t pv_api;
+
+stat_var *requests;
+stat_var *replies;
+stat_var *errors;
+stat_var *timeouts;
+
+static cmd_export_t cmds[]={
+	{"http_async_query",  (cmd_function)w_http_async_get, 2, fixup_http_async_get,
+		0, ANY_ROUTE},
+	{"http_async_query", (cmd_function)w_http_async_post, 3, fixup_http_async_post,
+		0, ANY_ROUTE},
+	{"http_verify_host", (cmd_function)w_http_verify_host, 1, fixup_igp_all,
+		0, ANY_ROUTE},
+	{"http_verify_peer", (cmd_function)w_http_verify_peer, 1, fixup_igp_all,
+		0, ANY_ROUTE},
+	{"http_async_suspend", (cmd_function)w_http_async_suspend_transaction, 1, fixup_igp_all,
+		0, ANY_ROUTE},
+	{"http_set_timeout", (cmd_function)w_http_set_timeout, 1, fixup_igp_all,
+		0, ANY_ROUTE},
+	{"http_append_header", (cmd_function)w_http_append_header, 1, fixup_spve_null,
+		0, ANY_ROUTE},
+	{"http_set_method", (cmd_function)w_http_set_method, 1, fixup_spve_null,
+		0, ANY_ROUTE},
+	{"http_set_ssl_cert", (cmd_function)w_http_set_ssl_cert, 1, fixup_spve_null,
+		0, ANY_ROUTE},
+	{"http_set_ssl_key", (cmd_function)w_http_set_ssl_key, 1, fixup_spve_null,
+		0, ANY_ROUTE},
+	{"http_set_ca_path", (cmd_function)w_http_set_ca_path, 1, fixup_spve_null,
+		0, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[]={
+{"workers",					INT_PARAM,   &num_workers},
+	{"connection_timeout",	INT_PARAM,   &http_timeout},
+	{"hash_size",			INT_PARAM,   &hash_size},
+	{"tlsversion",			INT_PARAM,   &ssl_version},
+	{"tlsverifyhost",		INT_PARAM,   &verify_host},
+	{"tlsverifypeer",		INT_PARAM,   &verify_peer},
+	{"curl_verbose",		INT_PARAM,   &curl_verbose},
+	{"tlsclientcert",		PARAM_STR,   &ssl_cert},
+	{"tlsclientkey",		PARAM_STR,   &ssl_key},
+	{"tlscapath",			PARAM_STR,   &ca_path},
+	{"memory_manager",	PARAM_STRING,&memory_manager},
+	{0, 0, 0}
+};
+
+/*! \brief We expose internal variables via the statistic framework below.*/
+stat_export_t mod_stats[] = {
+        {"requests",    STAT_NO_RESET, &requests        },
+        {"replies", 	STAT_NO_RESET, &replies 	},
+        {"errors",      STAT_NO_RESET, &errors       	},
+        {"timeouts",    STAT_NO_RESET, &timeouts	},
+        {0, 0, 0}
+};
+
+static pv_export_t pvs[] = {
+	{STR_STATIC_INIT("http_hdr"),
+		PVT_HDR, ah_get_hdr, 0,
+		w_pv_parse_hdr_name, pv_parse_index, 0, 0},
+	{STR_STATIC_INIT("http_rr"),
+		PVT_OTHER, ah_get_reason, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_rs"),
+		PVT_OTHER, ah_get_status, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_rb"),
+		PVT_MSG_BODY, ah_get_msg_body, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_bs"),
+		PVT_OTHER, ah_get_body_size, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_mb"),
+		PVT_OTHER, ah_get_msg_buf, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_ml"),
+		PVT_OTHER, ah_get_msg_len, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_ok"),
+		PVT_OTHER, ah_get_ok, 0,
+		0, 0, 0, 0},
+	{STR_STATIC_INIT("http_err"),
+		PVT_OTHER, ah_get_err, 0,
+		0, 0, 0, 0},
+	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+struct module_exports exports = {
+	"http_async_client",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,
+	mod_stats,   	/* exported statistics */
+	0,              /* exported MI functions */
+	pvs,            /* exported pseudo-variables */
+	0,              /* extra processes */
+	mod_init,       /* module initialization function */
+	0,              /* response function */
+	mod_destroy,    /* destroy function */
+	child_init      /* per child init function */
+};
+
+
+
+/**
+ * init module function
+ */
+static int mod_init(void)
+{
+	unsigned int n;
+	pv_register_api_t pvra;
+	LM_INFO("Initializing Http Async module\n");
+
+#ifdef STATISTICS
+	/* register statistics */
+	if (register_module_stats( exports.name, mod_stats)!=0 ) {
+		LM_ERR("failed to register core statistics\n");
+		return -1;
+	}
+#endif
+	/* sanitize hash_size */
+	if (hash_size < 1){
+		LM_WARN("hash_size is smaller "
+				"than 1  -> rounding from %d to 1\n",
+				hash_size);
+				hash_size = 1;
+	}
+	/* check that the hash table size is a power of 2 */
+	for( n=0 ; n<(8*sizeof(n)) ; n++) {
+		if (hash_size==(1<<n))
+			break;
+		if (n && hash_size<(1<<n)) {
+			LM_WARN("hash_size is not a power "
+				"of 2 as it should be -> rounding from %d to %d (n=%d)\n",
+				hash_size, 1<<(n-1), n);
+			hash_size = 1<<(n-1);
+			break;
+		}
+	}
+	/* check 'workers' param */
+	if (num_workers < 1) {
+		LM_ERR("the 'workers' parameter must be >= 1\n");
+		return -1;
+	}
+
+	verify_host = verify_host?1:0;
+	verify_peer = verify_peer?1:0;
+
+	/* init http parameters list */
+	init_query_params(&ah_params);
+
+	if (strncmp("shm", memory_manager, 3) == 0) {
+		curl_memory_manager = 0;
+	} else if (strncmp("sys", memory_manager, 3) == 0) {
+		curl_memory_manager = 1;
+	} else {
+		LM_ERR("invalid memory_manager parameter: '%s'\n", memory_manager);
+		return -1;
+	}
+
+	if (strncmp("shm", memory_manager, 3) == 0) {
+		curl_memory_manager = 0;
+	} else if (strncmp("sys", memory_manager, 3) == 0) {
+		curl_memory_manager = 1;
+	} else {
+		LM_ERR("invalid memory_manager parameter: '%s'\n", memory_manager);
+		return -1;
+	}
+
+	/* init faked sip msg */
+	if(faked_msg_init()<0) {
+		LM_ERR("failed to init faked sip msg\n");
+		return -1;
+	}
+
+	if(load_tm_api( &tmb ) < 0) {
+		LM_INFO("cannot load the TM-functions - async relay disabled\n");
+		memset(&tmb, 0, sizeof(tm_api_t));
+	}
+
+	pvra = (pv_register_api_t)find_export("pv_register_api", NO_SCRIPT, 0);
+	if (!pvra) {
+		LM_ERR("Cannot import pv functions (pv module must be loaded before this module)\n");
+		return -1;
+	}
+	pvra(&pv_api);
+
+	/* allocate workers array */
+	workers = shm_malloc(num_workers * sizeof(*workers));
+	if(workers == NULL) {
+		LM_ERR("error in shm_malloc\n");
+		return -1;
+	}
+	memset(workers, 0, num_workers * sizeof(*workers));
+
+	register_procs(num_workers);
+
+	/* add child to update local config framework structures */
+	cfg_register_child(num_workers);
+
+	return 0;
+}
+
+/**
+ * @brief Initialize async module children
+ */
+static int child_init(int rank)
+{
+	int pid;
+	int i;
+
+	LM_DBG("child initializing async http\n");
+
+	if(num_workers<=0)
+		return 0;
+
+	if (rank==PROC_INIT) {
+		for(i=0; i<num_workers; i++) {
+			LM_DBG("initializing worker notification socket: %d\n", i);
+			if(async_http_init_sockets(&workers[i])<0) {
+				LM_ERR("failed to initialize tasks sockets\n");
+				return -1;
+			}
+		}
+
+		return 0;
+	}
+
+	if(rank>0) {
+		for(i=0; i<num_workers; i++) {
+			close(workers[i].notication_socket[0]);
+		}
+		return 0;
+	}
+	if (rank!=PROC_MAIN)
+		return 0;
+
+	for(i=0; i<num_workers; i++) {
+		if(async_http_init_worker(i+1, &workers[i])<0) {
+			LM_ERR("failed to initialize worker process: %d\n", i);
+			return -1;
+		}
+		pid=fork_process(PROC_RPC, "Http Worker", 1);
+		if (pid<0)
+			return -1; /* error */
+		if(pid==0) {
+			/* child */
+			/* enforce http_reply_parse=yes */
+			http_reply_parse = 1;
+			/* initialize the config framework */
+			if (cfg_child_init())
+				return -1;
+			/* init msg structure for http reply parsing */
+			ah_reply = pkg_malloc(sizeof(struct sip_msg));
+			if(!ah_reply) {
+				LM_ERR("failed to allocate pkg memory\n");
+				return -1;
+			}
+			memset(ah_reply, 0, sizeof(struct sip_msg));
+			/* main function for workers */
+			async_http_run_worker(&workers[i]);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * destroy module function
+ */
+static void mod_destroy(void)
+{
+}
+
+/**
+ *
+ */
+static int w_http_async_get(sip_msg_t *msg, char *query, char* rt)
+{
+	str sdata;
+	cfg_action_t *act;
+	str rn;
+	int ri;
+
+	if(msg==NULL)
+		return -1;
+
+	if(fixup_get_svalue(msg, (gparam_t*)query, &sdata)!=0) {
+		LM_ERR("unable to get data\n");
+		return -1;
+	}
+	if(sdata.s==NULL || sdata.len == 0) {
+		LM_ERR("invalid data parameter\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_t*)rt, &rn)!=0)
+	{
+		LM_ERR("no route block name\n");
+		return -1;
+	}
+
+	ri = route_get(&main_rt, rn.s);
+	if(ri<0)
+	{
+		LM_ERR("unable to find route block [%.*s]\n", rn.len, rn.s);
+		return -1;
+	}
+	act = main_rt.rlist[ri];
+	if(act==NULL)
+	{
+		LM_ERR("empty action lists in route block [%.*s]\n", rn.len, rn.s);
+		return -1;
+	}
+
+	return async_send_query(msg, &sdata, NULL, act);
+
+}
+
+/**
+ *
+ */
+static int w_http_async_post(sip_msg_t *msg, char *query, char* post, char* rt)
+{
+	str sdata;
+	str post_data;
+	cfg_action_t *act;
+	str rn;
+	int ri;
+
+	if(msg==NULL)
+		return -1;
+
+	if(fixup_get_svalue(msg, (gparam_t*)query, &sdata)!=0) {
+		LM_ERR("unable to get data\n");
+		return -1;
+	}
+
+	if(sdata.s==NULL || sdata.len == 0) {
+		LM_ERR("invalid data parameter\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_t*)post, &post_data)!=0) {
+		LM_ERR("unable to get post data\n");
+		return -1;
+	}
+
+	if(post_data.s==NULL || post_data.len == 0) {
+		LM_ERR("invalid post data parameter\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_t*)rt, &rn)!=0)
+	{
+		LM_ERR("no route block name\n");
+		return -1;
+	}
+
+	ri = route_get(&main_rt, rn.s);
+	if(ri<0)
+	{
+		LM_ERR("unable to find route block [%.*s]\n", rn.len, rn.s);
+		return -1;
+	}
+	act = main_rt.rlist[ri];
+	if(act==NULL)
+	{
+		LM_ERR("empty action lists in route block [%.*s]\n", rn.len, rn.s);
+		return -1;
+	}
+
+	if(async_send_query(msg, &sdata, &post_data, act)<0)
+		return -1;
+
+	/* force exit in config */
+	return 0;
+}
+
+#define _IVALUE_ERROR(NAME) LM_ERR("invalid parameter '" #NAME "' (must be a number)\n")
+#define _IVALUE(NAME)\
+int i_##NAME ;\
+if(fixup_get_ivalue(msg, (gparam_t*)NAME, &( i_##NAME))!=0)\
+{ \
+	_IVALUE_ERROR(NAME);\
+	return -1;\
+}
+
+static int w_http_verify_host(sip_msg_t* msg, char* vh, char*foo)
+{
+	_IVALUE (vh);
+	ah_params.verify_host = i_vh?1:0;
+	return 1;
+}
+
+static int w_http_verify_peer(sip_msg_t* msg, char* vp, char*foo)
+{
+	_IVALUE (vp);
+	ah_params.verify_peer = i_vp?1:0;
+	return 1;
+}
+
+static int w_http_async_suspend_transaction(sip_msg_t* msg, char* vp, char*foo)
+{
+	_IVALUE (vp);
+	ah_params.suspend_transaction = i_vp?1:0;
+	return 1;
+}
+
+static int w_http_set_timeout(sip_msg_t* msg, char* tout, char*foo)
+{
+	_IVALUE (tout);
+	if (i_tout < 0) {
+		LM_ERR("timeout must be >= 0 (got %d)\n", i_tout);
+		return -1;
+	}
+	ah_params.timeout = i_tout;
+	return 1;
+}
+
+static int w_http_append_header(sip_msg_t* msg, char* hdr, char*foo)
+{
+	str shdr;
+
+	if(fixup_get_svalue(msg, (gparam_t*)hdr, &shdr)!=0) {
+		LM_ERR("unable to get header value\n");
+		return -1;
+	}
+
+	ah_params.headers.len++;
+	ah_params.headers.t = shm_realloc(ah_params.headers.t, ah_params.headers.len * sizeof(char*));
+	if (!ah_params.headers.t) {
+		LM_ERR("shm memory allocation failure\n");
+		return -1;
+	}
+	ah_params.headers.t[ah_params.headers.len - 1] = shm_malloc(shdr.len + 1);
+	if (!ah_params.headers.t[ah_params.headers.len - 1]) {
+		LM_ERR("shm memory allocation failure\n");
+		return -1;
+	}
+	memcpy(ah_params.headers.t[ah_params.headers.len - 1], shdr.s, shdr.len);
+	*(ah_params.headers.t[ah_params.headers.len - 1] + shdr.len) = '\0';
+
+	LM_DBG("stored new http header: [%s]\n",
+			ah_params.headers.t[ah_params.headers.len - 1]);
+
+	return 1;
+}
+
+static int w_http_set_method(sip_msg_t* msg, char* meth, char*foo)
+{
+	str smeth;
+
+	if(fixup_get_svalue(msg, (gparam_t*)meth, &smeth)!=0) {
+		LM_ERR("unable to get method value\n");
+		return -1;
+	}
+
+	if (strncasecmp(smeth.s, "GET", smeth.len) == 0) {
+		ah_params.method = AH_METH_GET;
+	} else if (strncasecmp(smeth.s, "POST", smeth.len) == 0) {
+		ah_params.method = AH_METH_POST;
+	} else if (strncasecmp(smeth.s, "PUT", smeth.len) == 0) {
+		ah_params.method = AH_METH_PUT;
+	} else if (strncasecmp(smeth.s, "DELETE", smeth.len) == 0) {
+		ah_params.method = AH_METH_DELETE;
+	} else {
+		LM_ERR("Unsupported method: %.*s\n", smeth.len, smeth.s);
+		return -1;
+	}
+
+	return 1;
+}
+
+static int w_http_set_ssl_cert(sip_msg_t* msg, char* sc, char*foo)
+{
+	str _ssl_cert;
+
+	if(fixup_get_svalue(msg, (gparam_t*)sc, &_ssl_cert)!=0) {
+		LM_ERR("unable to get method value\n");
+		return -1;
+	}
+
+	if (ah_params.ssl_cert.s) {
+		shm_free(ah_params.ssl_cert.s);
+		ah_params.ssl_cert.s = NULL;
+		ah_params.ssl_cert.len = 0;
+	}
+
+	if (_ssl_cert.s && _ssl_cert.len > 0) {
+		if (shm_str_dup(&ah_params.ssl_cert, &_ssl_cert) < 0) {
+			LM_ERR("Error allocating ah_params.ssl_cert\n");
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+static int w_http_set_ssl_key(sip_msg_t* msg, char* sk, char*foo)
+{
+	str _ssl_key;
+
+	if(fixup_get_svalue(msg, (gparam_t*)sk, &_ssl_key)!=0) {
+		LM_ERR("unable to get method value\n");
+		return -1;
+	}
+
+	if (ah_params.ssl_key.s) {
+		shm_free(ah_params.ssl_key.s);
+		ah_params.ssl_key.s = NULL;
+		ah_params.ssl_key.len = 0;
+	}
+
+	if (_ssl_key.s && _ssl_key.len > 0) {
+		if (shm_str_dup(&ah_params.ssl_key, &_ssl_key) < 0) {
+			LM_ERR("Error allocating ah_params.ssl_key\n");
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+static int w_http_set_ca_path(sip_msg_t* msg, char* cp, char*foo)
+{
+	str _ca_path;
+
+	if(fixup_get_svalue(msg, (gparam_t*)cp, &_ca_path)!=0) {
+		LM_ERR("unable to get method value\n");
+		return -1;
+	}
+
+	if (ah_params.ca_path.s) {
+		shm_free(ah_params.ca_path.s);
+		ah_params.ca_path.s = NULL;
+		ah_params.ca_path.len = 0;
+	}
+
+	if (_ca_path.s && _ca_path.len > 0) {
+		if (shm_str_dup(&ah_params.ca_path, &_ca_path) < 0) {
+			LM_ERR("Error allocating ah_params.ca_path\n");
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+/**
+ *
+ */
+static int fixup_http_async_get(void** param, int param_no)
+{
+	if (param_no == 1) {
+		return fixup_spve_null(param, 1);
+	}
+	if (param_no == 2) {
+		return fixup_var_str_12(param, param_no);
+	}
+
+	LM_ERR("invalid parameter number <%d>\n", param_no);
+	return -1;
+}
+
+/**
+ *
+ */
+static int fixup_http_async_post(void** param, int param_no)
+{
+	if (param_no == 1 || param_no == 2) {
+		return fixup_spve_null(param, 1);
+	}
+	if (param_no == 3) {
+		return fixup_var_str_12(param, param_no);
+	}
+
+	LM_ERR("invalid parameter number <%d>\n", param_no);
+	return -1;
+}
+
+/* module PVs */
+
+#define AH_WRAP_GET_PV(AH_F, PV_F) static int AH_F(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) \
+	{ \
+		if (ah_reply) { \
+			if (ah_error.s) { \
+				LM_WARN("an async_http variable was read after http error, use $ah_ok to check the request's status\n"); \
+				return pv_get_null(msg, param, res); \
+			} else { \
+				return pv_api.PV_F(ah_reply, param, res); \
+			} \
+		} else { \
+			LM_ERR("the async_http variables can only be read from an async http worker\n"); \
+			return pv_get_null(msg, param, res); \
+		} \
+	}
+
+AH_WRAP_GET_PV(ah_get_reason,      get_reason)
+AH_WRAP_GET_PV(ah_get_hdr,         get_hdr)
+AH_WRAP_GET_PV(ah_get_status,      get_status)
+AH_WRAP_GET_PV(ah_get_msg_body,    get_msg_body)
+AH_WRAP_GET_PV(ah_get_body_size,   get_body_size)
+AH_WRAP_GET_PV(ah_get_msg_buf,     get_msg_buf)
+AH_WRAP_GET_PV(ah_get_msg_len,     get_msg_len)
+
+static int w_pv_parse_hdr_name(pv_spec_p sp, str *in) {
+	return pv_api.parse_hdr_name(sp, in);
+}
+
+static int ah_get_ok(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) {
+	if (ah_reply) {
+		if (ah_error.s) {
+			return pv_get_intstrval(msg, param, res, 0, &pv_str_0);
+		} else {
+			return pv_get_intstrval(msg, param, res, 1, &pv_str_1);
+		}
+	} else {
+		LM_ERR("the async_http variables can only be read from an async http worker\n");
+		return pv_get_null(msg, param, res);
+	}
+}
+
+static int ah_get_err(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) {
+	if (ah_reply) {
+		if (ah_error.s) {
+			return pv_get_strval(msg, param, res, &ah_error);
+		} else {
+			return pv_get_null(msg, param, res);
+		}
+	} else {
+		LM_ERR("the async_http variables can only be read from an async http worker\n");
+		return pv_get_null(msg, param, res);
+	}
+}

+ 623 - 0
modules/http_async_client/http_multi.c

@@ -0,0 +1,623 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../ut.h"
+#include "../../hashes.h"
+#include "http_multi.h"
+
+extern int hash_size;
+/*! global http multi table */
+struct http_m_table *hm_table = 0;
+struct http_m_global *g = 0;
+
+/* 0: shm, 1:system malloc */
+int curl_memory_manager = 0;
+
+/* Update the event timer after curl_multi library calls */
+int multi_timer_cb(CURLM *multi, long timeout_ms, struct http_m_global *g)
+{
+	struct timeval timeout;
+	(void)multi; /* unused */
+
+	timeout.tv_sec = timeout_ms/1000;
+	timeout.tv_usec = (timeout_ms%1000)*1000;
+	LM_DBG("multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms);
+	evtimer_add(g->timer_event, &timeout);
+	return 0;
+}
+/* Called by libevent when our timeout expires */
+void timer_cb(int fd, short kind, void *userp)
+{
+	struct http_m_global *g = (struct http_m_global *)userp;
+	CURLMcode rc;
+	(void)fd;
+	(void)kind;
+
+	char error[CURL_ERROR_SIZE];
+
+	LM_DBG("timeout on socket %d\n", fd);
+
+	rc = curl_multi_socket_action(g->multi,
+                                  CURL_SOCKET_TIMEOUT, 0, &g->still_running);
+	if (check_mcode(rc, error) < 0) {
+		LM_ERR("curl_multi_socket_action error: %s", error);
+	}
+
+	check_multi_info(g);
+}
+/* Called by libevent when we get action on a multi socket */
+void event_cb(int fd, short kind, void *userp)
+{
+	struct http_m_global *g;
+	CURLMcode rc;
+	CURL *easy = (CURL*) userp;
+	struct http_m_cell *cell;
+
+	cell = http_m_cell_lookup(easy);
+	if (cell == NULL) {
+		LM_INFO("Cell for handler %p not found in table\n", easy);
+		return;
+	}
+
+	g = cell->global;
+	int action =
+		(kind & EV_READ ? CURL_CSELECT_IN : 0) |
+		(kind & EV_WRITE ? CURL_CSELECT_OUT : 0);
+
+	LM_DBG("activity %d on socket %d: action %d\n", kind, fd, action);
+	if (kind == EV_TIMEOUT) {
+		LM_DBG("handle %p timeout on socket %d (cell=%p, param=%p)\n", cell->easy, fd, cell, cell->param);
+		update_stat(timeouts, 1);
+		const char *error = "TIMEOUT";
+
+		strncpy(cell->error, error, strlen(error)+1);
+
+		reply_error(cell);
+
+		easy = cell->easy;
+		/* we are going to remove the cell and the handle here:
+		   pass NULL as sockptr */
+		curl_multi_assign(g->multi, cell->sockfd, NULL);
+
+		LM_DBG("cleaning up cell %p\n", cell);
+		if (cell->evset && cell->ev) {
+			LM_DBG("freeing event %p\n", cell->ev);
+			event_del(cell->ev);
+			event_free(cell->ev);
+			cell->ev=NULL;
+			cell->evset=0;
+		}
+		unlink_http_m_cell(cell);
+		free_http_m_cell(cell);
+
+		LM_DBG("removing handle %p\n", easy);
+		curl_multi_remove_handle(g->multi, easy);
+		curl_easy_cleanup(easy);
+		rc = curl_multi_socket_action(g->multi,
+                                  CURL_SOCKET_TIMEOUT, 0, &g->still_running);
+
+	} else {
+		LM_DBG("performing action %d on socket %d\n", action, fd);
+		rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
+		LM_DBG("action %d on socket %d performed\n", action, fd);
+
+		if (rc == CURLM_CALL_MULTI_PERFORM) {
+			LM_DBG("received CURLM_CALL_MULTI_PERFORM, performing action again\n");
+			rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
+		}
+		if (check_mcode(rc, cell->error) < 0) {
+			LM_ERR("error: %s\n", cell->error);
+			reply_error(cell);
+			curl_multi_remove_handle(g->multi, easy);
+			curl_easy_cleanup(easy);
+		}
+	}
+
+	check_multi_info(g);
+	if ( g->still_running <= 0 ) {
+		LM_DBG("last transfer done, kill timeout\n");
+		if (evtimer_pending(g->timer_event, NULL)) {
+			evtimer_del(g->timer_event);
+		}
+	}
+}
+
+/* CURLMOPT_SOCKETFUNCTION */
+int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
+{
+	struct http_m_global *g = (struct http_m_global*) cbp;
+	struct http_m_cell *cell = (struct http_m_cell*)sockp;
+	const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
+
+	LM_DBG("socket callback: s=%d e=%p what=%s\n", s, e, whatstr[what]);
+	if (what == CURL_POLL_REMOVE) {
+		/* if cell is NULL the handle has been removed by the event callback for timeout */
+		if (cell) {
+			if (cell->evset && cell->ev) {
+				LM_DBG("freeing event %p\n", cell->ev);
+				event_del(cell->ev);
+				event_free(cell->ev);
+				cell->ev=NULL;
+				cell->evset=0;
+			}
+		}
+		else {
+			LM_DBG("REMOVE action without cell, handler timed out.\n");
+		}
+	}
+	else {
+		if (!cell) {
+			LM_DBG("Adding data: %s\n", whatstr[what]);
+			addsock(s, e, what, g);
+		}
+		else {
+			LM_DBG("Changing action from %s to %s\n",
+			whatstr[cell->action], whatstr[what]);
+			setsock(cell, s, e, what);
+		}
+	}
+	return 0;
+}
+int check_mcode(CURLMcode code, char *error)
+{
+	const char *s;
+	if ( CURLM_OK != code && CURLM_CALL_MULTI_PERFORM != code ) {
+		switch (code) {
+			case     CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
+			case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
+			case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
+			case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
+			case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
+			case     CURLM_LAST:               s="CURLM_LAST";               break;
+			case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";	   break;
+			default: s="CURLM_unknown";
+			  break;
+		}
+		LM_ERR("ERROR: %s\n", s);
+		strncpy(error, s, strlen(s)+1);
+		return -1;
+	}
+	return 0;
+}
+
+/* CURLOPT_DEBUGFUNCTION */
+int debug_cb(CURL *handle,
+                   curl_infotype type,
+                   char *data,
+                   size_t size,
+                   void *userptr)
+{
+	char *prefix;
+	switch (type) {
+	case CURLINFO_TEXT:
+		prefix = "[cURL]";
+		break;
+	case CURLINFO_HEADER_IN:
+		prefix = "[cURL hdr in]";
+		break;
+	case CURLINFO_HEADER_OUT:
+		prefix = "[cURL hdr out]";
+		break;
+	case CURLINFO_DATA_IN:
+	case CURLINFO_DATA_OUT:
+	case CURLINFO_SSL_DATA_OUT:
+	case CURLINFO_SSL_DATA_IN:
+	default:
+		return 0;
+		break;
+	}
+	LM_INFO("%s %.*s"/* cURL includes final \n */, prefix, (int)size, data);
+	return 0;
+}
+/* CURLOPT_WRITEFUNCTION */
+size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
+{
+	size_t realsize = size * nmemb;
+	struct http_m_cell *cell;
+	CURL *easy = (CURL*) data;
+	int old_len;
+
+	LM_DBG("data received: %.*s [%d]\n", (int)realsize, (char*)ptr, (int)realsize);
+
+	cell = http_m_cell_lookup(easy);
+	if (cell == NULL) {
+		LM_ERR("Cell for handler %p not found in table\n", easy);
+		return -1;
+	}
+
+	if (cell->reply == NULL) {
+		cell->reply = (struct http_m_reply*)shm_malloc(sizeof(struct http_m_reply));
+		if (cell->reply == NULL) {
+			LM_ERR("Cannot allocate shm memory for reply\n");
+			return -1;
+		}
+		memset( cell->reply, 0, sizeof(struct http_m_reply) );
+		cell->reply->result = (str *)shm_malloc(sizeof(str));
+		if (cell->reply->result == NULL) {
+			LM_ERR("Cannot allocate shm memory for reply's result\n");
+			shm_free(cell->reply);
+			return -1;
+		}
+		memset( cell->reply->result, 0, sizeof(str) );
+	}
+
+	old_len = cell->reply->result->len;
+	cell->reply->result->len += realsize;
+	cell->reply->result->s = (char*)shm_realloc(cell->reply->result->s, cell->reply->result->len);
+	if (cell->reply->result->s == NULL) {
+		LM_ERR("Cannot allocate shm memory for reply's result\n");
+		shm_free(cell->reply->result);
+		shm_free(cell->reply);
+		cell->reply = NULL;
+		return -1;
+	}
+	strncpy(cell->reply->result->s + old_len, ptr, cell->reply->result->len - old_len);
+
+	if (cell->easy == NULL ) { /* TODO: when does this happen? */
+		LM_DBG("cell %p easy handler is null\n", cell);
+	}
+	else {
+		LM_DBG("getting easy handler info (%p)\n", cell->easy);
+		curl_easy_getinfo(cell->easy, CURLINFO_HTTP_CODE, &cell->reply->retcode);
+	}
+
+	return realsize;
+}
+
+void reply_error(struct http_m_cell *cell)
+{
+	struct http_m_reply *reply;
+	LM_DBG("replying error for  cell=%p\n", cell);
+
+	reply = (struct http_m_reply*)pkg_malloc(sizeof(struct http_m_reply));
+	if (reply == NULL) {
+		LM_ERR("Cannot allocate pkg memory for reply's result\n");
+		return;
+	}
+	memset( reply, 0, sizeof(struct http_m_reply) );
+	reply->result = NULL;
+	reply->retcode = 0;
+
+	if (cell) {
+		strncpy(reply->error, cell->error, strlen(cell->error));
+		reply->error[strlen(cell->error)] = '\0';
+	} else {
+		reply->error[0] = '\0';
+	}
+
+	cell->cb(reply, cell->param);
+
+	return;
+}
+
+static void *curl_shm_malloc(size_t size)
+{
+    void *p = shm_malloc(size);
+    return p;
+}
+static void curl_shm_free(void *ptr)
+{
+	if (ptr)
+		shm_free(ptr);
+}
+
+static void *curl_shm_realloc(void *ptr, size_t size)
+{
+    void *p = shm_realloc(ptr, size);
+
+    return p;
+}
+
+static void *curl_shm_calloc(size_t nmemb, size_t size)
+{
+    void *p = shm_malloc(nmemb * size);
+    if (p)
+        memset(p, '\0', nmemb * size);
+
+    return p;
+}
+
+static char *curl_shm_strdup(const char *cp)
+{
+    char *rval;
+    int len;
+
+    len = strlen(cp) + 1;
+    rval = shm_malloc(len);
+    if (!rval)
+        return NULL;
+
+    memcpy(rval, cp, len);
+    return rval;
+}
+
+void set_curl_mem_callbacks(void)
+{
+	CURLcode rc;
+
+	switch (curl_memory_manager) {
+		case 0:
+			LM_DBG("Setting shm memory callbacks for cURL\n");
+			rc = curl_global_init_mem(CURL_GLOBAL_ALL,
+		                        curl_shm_malloc,
+		                        curl_shm_free,
+		                        curl_shm_realloc,
+		                        curl_shm_strdup,
+		                        curl_shm_calloc);
+			if (rc != 0) {
+				LM_ERR("Cannot set memory callbacks for cURL: %d\n", rc);
+			}
+			break;
+		case 1:
+			LM_DBG("Initilizing cURL with sys malloc\n");
+			rc = curl_global_init(CURL_GLOBAL_ALL);
+			if (rc != 0) {
+				LM_ERR("Cannot initialize cURL: %d\n", rc);
+			}
+			break;
+		default:
+			LM_ERR ("invalid memory manager: %d\n", curl_memory_manager);
+			break;
+	}
+
+}
+
+int init_http_multi(struct event_base *evbase, struct http_m_global *wg)
+{
+	g = wg;
+	g->evbase = evbase;
+
+	set_curl_mem_callbacks();
+
+	g->multi = curl_multi_init();
+	LM_DBG("curl_multi %p initialized on global %p (evbase %p)\n", g->multi, g, evbase);
+
+    g->timer_event = evtimer_new(g->evbase, timer_cb, g);
+
+	/* setup the generic multi interface options we want */
+	curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
+	curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
+	curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+	curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, g);
+
+	return init_http_m_table(hash_size);
+}
+
+int new_request(str *query, str *post, http_m_params_t *query_params, http_multi_cbe_t cb, void *param)
+{
+
+	LM_DBG("received query %.*s with timeout %d, verify_peer %d, verify_host %d (param=%p)\n", 
+			query->len, query->s, query_params->timeout, query_params->verify_peer, query_params->verify_host, param);
+	
+	CURL *easy;
+	CURLMcode rc;
+
+	struct http_m_cell *cell;
+
+	update_stat(requests, 1);
+
+	easy = NULL;
+	cell = NULL;
+
+	easy = curl_easy_init();
+	if (!easy) {
+		LM_ERR("curl_easy_init() failed!\n");
+		update_stat(errors, 1);
+		return -1;
+	}
+
+	cell = build_http_m_cell(easy);
+	if (!cell) {
+		LM_ERR("cannot create cell!\n");
+		update_stat(errors, 1);
+		LM_DBG("cleaning up curl handler %p\n", easy);
+		curl_easy_cleanup(easy);
+		return -1;
+	}
+
+	link_http_m_cell(cell);
+
+	cell->global = g;
+	cell->easy=easy;
+	cell->error[0] = '\0';
+	cell->params = *query_params;
+	cell->param = param;
+	cell->cb = cb;
+	cell->url = (char*)shm_malloc(query->len + 1);
+	if (cell->url==0) {
+		LM_ERR("no more shm mem\n");
+        goto error;
+	}
+	strncpy(cell->url, query->s, query->len);
+	cell->url[query->len] = '\0';
+
+	curl_easy_setopt(cell->easy, CURLOPT_URL, cell->url);
+	curl_easy_setopt(cell->easy, CURLOPT_WRITEFUNCTION, write_cb);
+	curl_easy_setopt(cell->easy, CURLOPT_WRITEDATA, easy);
+	if (curl_verbose) {
+		curl_easy_setopt(cell->easy, CURLOPT_VERBOSE, 1L);
+		curl_easy_setopt(cell->easy, CURLOPT_DEBUGFUNCTION, debug_cb);
+	}
+	curl_easy_setopt(cell->easy, CURLOPT_ERRORBUFFER, cell->error);
+	curl_easy_setopt(cell->easy, CURLOPT_PRIVATE, cell);
+	curl_easy_setopt(cell->easy, CURLOPT_SSL_VERIFYPEER, cell->params.verify_peer);
+	curl_easy_setopt(cell->easy, CURLOPT_SSL_VERIFYHOST, cell->params.verify_host?2:0);
+	curl_easy_setopt(cell->easy, CURLOPT_SSLVERSION, ssl_version);
+
+	if (cell->params.ssl_cert.s && cell->params.ssl_cert.len > 0) {
+		curl_easy_setopt(cell->easy, CURLOPT_SSLCERT, cell->params.ssl_cert.s);
+	}
+
+	if (cell->params.ssl_key.s && cell->params.ssl_key.len > 0) {
+		curl_easy_setopt(cell->easy, CURLOPT_SSLKEY, cell->params.ssl_key.s);
+	}
+
+	if (cell->params.ca_path.s && cell->params.ca_path.len > 0) {
+		curl_easy_setopt(cell->easy, CURLOPT_CAPATH, cell->params.ca_path.s);
+	}
+
+	curl_easy_setopt(cell->easy, CURLOPT_HEADER, 1);
+	if (cell->params.headers) {
+		curl_easy_setopt(cell->easy, CURLOPT_HTTPHEADER, cell->params.headers);
+	}
+
+	if (post && post->s && post->len) {
+		curl_easy_setopt(cell->easy, CURLOPT_POST, 1L);
+		cell->post_data = shm_malloc(post->len + 1);
+		if (cell->post_data == NULL) {
+			LM_ERR("cannot allocate pkg memory for post\n");
+            goto error;
+		}
+		strncpy(cell->post_data, post->s, post->len);
+		cell->post_data[post->len] = '\0';
+		curl_easy_setopt(cell->easy, CURLOPT_POSTFIELDS, cell->post_data);
+	}
+
+	switch (cell->params.method) {
+	case 1:
+		curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "GET");
+		break;
+	case 2:
+		curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "POST");
+		break;
+	case 3:
+		curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "PUT");
+		break;
+	case 4:
+		curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
+		break;
+	default:
+		break;
+	}
+	LM_DBG("Adding easy %p to multi %p (%.*s)\n", cell->easy, g->multi, query->len, query->s);
+	rc = curl_multi_add_handle(g->multi, cell->easy);
+	if (check_mcode(rc, cell->error) < 0) {
+		LM_ERR("error adding curl handler: %s\n", cell->error);
+        goto error;
+	}
+	/* note that the add_handle() will set a time-out to trigger very soon so
+	 *      that the necessary socket_action() call will be called by this app */
+	return 0;
+
+error:
+	update_stat(errors, 1);
+    if (easy) {
+		LM_DBG("cleaning up curl handler %p\n", easy);
+		curl_easy_cleanup(easy);
+    }
+    free_http_m_cell(cell);
+    return -1;
+}
+
+/* Check for completed transfers, and remove their easy handles */
+void check_multi_info(struct http_m_global *g)
+{
+	char *eff_url;
+	CURLMsg *msg;
+	int msgs_left;
+	CURL *easy;
+	CURLcode res;
+
+	struct http_m_cell *cell;
+
+	LM_DBG("REMAINING: %d\n", g->still_running);
+	while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
+		if (msg->msg == CURLMSG_DONE) {
+			easy = msg->easy_handle;
+			res = msg->data.result;
+			curl_easy_getinfo(easy, CURLINFO_PRIVATE, &cell);
+			curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
+			LM_DBG("DONE: %s => (%d) %s\n", eff_url, res, cell->error);
+
+			cell = http_m_cell_lookup(easy);
+			if (msg->data.result != 0) {
+				LM_ERR("handle %p returned error %d: %s\n", easy, res, cell->error);
+				update_stat(errors, 1);
+				reply_error(cell);
+			} else {
+				cell->reply->error[0] = '\0';
+				cell->cb(cell->reply, cell->param);
+
+				LM_DBG("reply: [%d] %.*s [%d]\n", (int)cell->reply->retcode, cell->reply->result->len, cell->reply->result->s, cell->reply->result->len);
+				update_stat(replies, 1);
+			}
+
+			if (cell != 0) {
+				LM_DBG("cleaning up cell %p\n", cell);
+				unlink_http_m_cell(cell);
+				free_http_m_cell(cell);
+			}
+
+			LM_DBG("Removing handle %p\n", easy);
+			curl_multi_remove_handle(g->multi, easy);
+			curl_easy_cleanup(easy);
+		}
+	}
+}
+
+/* set cell's socket information and assign an event to the socket */
+void setsock(struct http_m_cell *cell, curl_socket_t s, CURL*e, int act)
+{
+
+	struct timeval timeout;
+
+	int kind =
+		(act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;
+	struct http_m_global *g = cell->global;
+	cell->sockfd = s;
+	cell->action = act;
+	cell->easy = e;
+	if (cell->evset && cell->ev) {
+		event_del(cell->ev);
+		event_free(cell->ev);
+		cell->ev=NULL;
+		cell->evset=0;
+	}
+	cell->ev = event_new(g->evbase, cell->sockfd, kind, event_cb, e);
+	LM_DBG("added event %p to socket %d\n", cell->ev, cell->sockfd);
+	cell->evset = 1;
+
+
+	timeout.tv_sec = cell->params.timeout/1000;
+	timeout.tv_usec = (cell->params.timeout%1000)*1000;
+
+	event_add(cell->ev, &timeout);
+}
+
+
+
+/* assign a socket to the multi handler */
+void addsock(curl_socket_t s, CURL *easy, int action, struct http_m_global *g)
+{
+	struct http_m_cell *cell;
+
+	cell = http_m_cell_lookup(easy);
+	if (!cell)
+		return;
+	setsock(cell, s, cell->easy, action);
+	curl_multi_assign(g->multi, s, cell);
+}
+

+ 65 - 0
modules/http_async_client/http_multi.h

@@ -0,0 +1,65 @@
+/**
+ * Copyright 2016 (C) Federico Cabiddu <[email protected]>
+ * Copyright 2016 (C) Giacomo Vacca <[email protected]>
+ * Copyright 2016 (C) Orange - Camille Oudot <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _HTTP_MULTI_
+#define _HTTP_MULTI_
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <curl/curl.h>
+#include <event2/event.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "../../lib/kcore/statistics.h"
+#include "hm_hash.h"
+
+
+extern stat_var *requests;
+extern stat_var *replies;
+extern stat_var *errors;
+extern stat_var *timeouts;
+extern int ssl_version;
+extern int curl_verbose;
+
+void set_curl_mem_callbacks(void);
+int init_http_multi();
+int multi_timer_cb(CURLM *multi, long timeout_ms, struct http_m_global *g);
+void timer_cb(int fd, short kind, void *userp);
+int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp);
+int check_mcode(CURLMcode code, char *error);
+int new_request(str *query, str *post, http_m_params_t *query_params, http_multi_cbe_t cb, void *param);
+void check_multi_info(struct http_m_global *g);
+void setsock(struct http_m_cell *cell, curl_socket_t s, CURL* e, int act);
+void addsock(curl_socket_t s, CURL *easy, int action, struct http_m_global *g);
+void event_cb(int fd, short kind, void *userp);
+void reply_error(struct http_m_cell *cell);
+
+#endif

+ 4 - 1
modules/pv/pv.c

@@ -40,6 +40,7 @@
 #ifdef WITH_XAVP
 #ifdef WITH_XAVP
 #include "pv_xavp.h"
 #include "pv_xavp.h"
 #endif
 #endif
+#include "pv_api.h"
 
 
 MODULE_VERSION
 MODULE_VERSION
 
 
@@ -502,6 +503,7 @@ static int w_var_to_xavp(sip_msg_t *msg, char *p1, char *p2);
 static int w_xavp_to_var(sip_msg_t *msg, char *p1);
 static int w_xavp_to_var(sip_msg_t *msg, char *p1);
 
 
 static int pv_init_rpc(void);
 static int pv_init_rpc(void);
+int pv_register_api(pv_api_t*);
 
 
 static cmd_export_t cmds[]={
 static cmd_export_t cmds[]={
 	{"pv_isset",  (cmd_function)pv_isset,  1, fixup_pvar_null, 0, 
 	{"pv_isset",  (cmd_function)pv_isset,  1, fixup_pvar_null, 0, 
@@ -533,7 +535,8 @@ static cmd_export_t cmds[]={
 		ANY_ROUTE },
 		ANY_ROUTE },
 	{"sbranch_reset",     (cmd_function)w_sbranch_reset,     0, 0, 0,
 	{"sbranch_reset",     (cmd_function)w_sbranch_reset,     0, 0, 0,
 		ANY_ROUTE },
 		ANY_ROUTE },
-
+	/* API exports */
+	{"pv_register_api",   (cmd_function)pv_register_api,     NO_SCRIPT, 0, 0},
 	{0,0,0,0,0,0}
 	{0,0,0,0,0,0}
 };
 };
 
 

+ 41 - 0
modules/pv/pv_api.c

@@ -0,0 +1,41 @@
+/**
+ * Copyright 2016 (C) Orange
+ * <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "pv_api.h"
+#include "pv_core.h"
+
+int pv_register_api(pv_api_t* api)
+{
+	if (!api)
+		return 0;
+
+	api->get_body_size = pv_get_body_size;
+	api->get_hdr = pv_get_hdr;
+	api->get_msg_body = pv_get_msg_body;
+	api->get_msg_buf = pv_get_msg_buf;
+	api->get_msg_len = pv_get_msg_len;
+	api->get_reason = pv_get_reason;
+	api->get_status = pv_get_status;
+	api->parse_hdr_name = pv_parse_hdr_name;
+	return 1;
+}

+ 37 - 0
modules/pv/pv_api.h

@@ -0,0 +1,37 @@
+/**
+ * Copyright 2016 (C) Orange
+ * <[email protected]>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../pvar.h"
+
+typedef struct _pv_api {
+	pv_getf_t get_reason;
+	pv_getf_t get_hdr;
+	pv_parse_name_f parse_hdr_name;
+	pv_getf_t get_status;
+	pv_getf_t get_msg_body;
+	pv_getf_t get_body_size;
+	pv_getf_t get_msg_buf;
+	pv_getf_t get_msg_len;
+} pv_api_t;
+
+typedef int (*pv_register_api_t)(pv_api_t*);