mirror of https://github.com/sipwise/rtpengine.git
Change-Id: I3ac04968279a5b750f1f7f4d4d06207e05e7d8a8pull/1611/head
parent
f861d984aa
commit
ff47e874ea
@ -0,0 +1,299 @@
|
||||
#include "notify.h"
|
||||
#include <stdbool.h>
|
||||
#include <curl/curl.h>
|
||||
#include "main.h"
|
||||
#include "log.h"
|
||||
#include "recaux.h"
|
||||
|
||||
|
||||
struct notif_req {
|
||||
char *name; // just for logging
|
||||
struct curl_slist *headers;
|
||||
|
||||
time_t retry_time;
|
||||
unsigned int retries;
|
||||
unsigned int falloff;
|
||||
};
|
||||
|
||||
|
||||
static GThreadPool *notify_threadpool;
|
||||
|
||||
static pthread_mutex_t timer_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t timer_cond = PTHREAD_COND_INITIALIZER;
|
||||
static pthread_t notify_waiter;
|
||||
static GTree *notify_timers;
|
||||
|
||||
|
||||
static size_t dummy_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||
return size * nmemb;
|
||||
}
|
||||
static size_t dummy_read(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void do_notify(void *p, void *u) {
|
||||
struct notif_req *req = p;
|
||||
const char *err = NULL;
|
||||
CURLcode ret;
|
||||
|
||||
ilog(LOG_DEBUG, "Launching HTTP notification for '%s%s%s'", FMT_M(req->name));
|
||||
|
||||
// set up the CURL request
|
||||
|
||||
CURL *c = curl_easy_init();
|
||||
if (!c)
|
||||
goto fail;
|
||||
|
||||
err = "setting CURLOPT_URL";
|
||||
ret = curl_easy_setopt(c, CURLOPT_URL, notify_uri);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// no output
|
||||
err = "setting CURLOPT_WRITEFUNCTION";
|
||||
ret = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, dummy_write);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// no input
|
||||
err = "setting CURLOPT_READFUNCTION";
|
||||
ret = curl_easy_setopt(c, CURLOPT_READFUNCTION, dummy_read);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// allow redirects
|
||||
err = "setting CURLOPT_FOLLOWLOCATION";
|
||||
ret = curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// max 5 redirects
|
||||
err = "setting CURLOPT_MAXREDIRS";
|
||||
ret = curl_easy_setopt(c, CURLOPT_MAXREDIRS, 5);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// add headers
|
||||
err = "setting CURLOPT_HTTPHEADER";
|
||||
ret = curl_easy_setopt(c, CURLOPT_HTTPHEADER, req->headers);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
// POST vs GET
|
||||
if (notify_post) {
|
||||
err = "setting CURLOPT_POST";
|
||||
ret = curl_easy_setopt(c, CURLOPT_POST, 1);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// cert verify (enabled by default)
|
||||
if (notify_nverify) {
|
||||
err = "setting CURLOPT_SSL_VERIFYPEER";
|
||||
ret = curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = "performing request";
|
||||
ret = curl_easy_perform(c);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
long code;
|
||||
err = "getting CURLINFO_RESPONSE_CODE";
|
||||
ret = curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &code);
|
||||
if (ret != CURLE_OK)
|
||||
goto fail;
|
||||
|
||||
err = "checking response code (not 2xx)";
|
||||
if (code < 200 || code >= 300)
|
||||
goto fail;
|
||||
|
||||
// success
|
||||
|
||||
ilog(LOG_NOTICE, "HTTP notification for '%s%s%s' was successful", FMT_M(req->name));
|
||||
goto cleanup;
|
||||
|
||||
fail:
|
||||
if (c)
|
||||
curl_easy_cleanup(c);
|
||||
|
||||
if (notify_retries >= 0 && req->retries < notify_retries) {
|
||||
// schedule retry
|
||||
req->retries++;
|
||||
if (c)
|
||||
ilog(LOG_DEBUG, "Failed to perform HTTP notification for '%s%s%s': "
|
||||
"Error while %s: %s. Will retry in %u seconds (#%u)",
|
||||
FMT_M(req->name),
|
||||
err, curl_easy_strerror(ret),
|
||||
req->falloff, req->retries);
|
||||
else
|
||||
ilog(LOG_DEBUG, "Failed to perform HTTP notification for '%s%s%s': "
|
||||
"Failed to create CURL object. Will retry in %u seconds (#%u)",
|
||||
FMT_M(req->name),
|
||||
req->falloff, req->retries);
|
||||
req->retry_time = time(NULL) + req->falloff;
|
||||
req->falloff *= 2;
|
||||
|
||||
pthread_mutex_lock(&timer_lock);
|
||||
g_tree_insert(notify_timers, req, req);
|
||||
pthread_cond_signal(&timer_cond);
|
||||
pthread_mutex_unlock(&timer_lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (c)
|
||||
ilog(LOG_ERR, "Failed to perform HTTP notification for '%s%s%s' after %u retries: "
|
||||
"Error while %s: %s",
|
||||
FMT_M(req->name),
|
||||
req->retries, err, curl_easy_strerror(ret));
|
||||
else
|
||||
ilog(LOG_ERR, "Failed to perform HTTP notification for '%s%s%s' after %u retries: "
|
||||
"Failed to create CURL object",
|
||||
FMT_M(req->name),
|
||||
req->retries);
|
||||
|
||||
c = NULL;
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (c)
|
||||
curl_easy_cleanup(c);
|
||||
curl_slist_free_all(req->headers);
|
||||
g_free(req->name);
|
||||
g_slice_free1(sizeof(*req), req);
|
||||
}
|
||||
|
||||
|
||||
static void *notify_timer(void *p) {
|
||||
pthread_mutex_lock(&timer_lock);
|
||||
|
||||
// notify_timers being NULL acts as our shutdown flag
|
||||
while (notify_timers) {
|
||||
ilog(LOG_DEBUG, "HTTP notification timer thread looping");
|
||||
|
||||
// grab first entry in list, check retry time, sleep if it's in the future
|
||||
|
||||
struct notif_req *first = g_tree_find_first(notify_timers, NULL, NULL);
|
||||
if (!first) {
|
||||
ilog(LOG_DEBUG, "No scheduled HTTP notification retries, sleeping");
|
||||
pthread_cond_wait(&timer_cond, &timer_lock);
|
||||
continue;
|
||||
}
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
if (now.tv_sec < first->retry_time) {
|
||||
ilog(LOG_DEBUG, "Sleeping until next scheduled HTTP notification retry in %lu seconds",
|
||||
(unsigned long) first->retry_time - now.tv_sec);
|
||||
struct timespec ts = {.tv_sec = first->retry_time, .tv_nsec = 0};
|
||||
pthread_cond_timedwait(&timer_cond, &timer_lock, &ts);
|
||||
continue;
|
||||
}
|
||||
|
||||
// first entry is ready to run
|
||||
|
||||
g_tree_remove(notify_timers, first);
|
||||
ilog(LOG_DEBUG, "HTTP notification retry for '%s%s%s' is scheduled now", FMT_M(first->name));
|
||||
g_thread_pool_push(notify_threadpool, first, NULL);
|
||||
}
|
||||
|
||||
// clean up
|
||||
|
||||
pthread_mutex_unlock(&timer_lock);
|
||||
pthread_mutex_destroy(&timer_lock);
|
||||
pthread_cond_destroy(&timer_cond);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static int notify_req_cmp(const void *A, const void *B) {
|
||||
const struct notif_req *a = A, *b = B;
|
||||
|
||||
if (a->retry_time < b->retry_time)
|
||||
return -1;
|
||||
if (a->retry_time > b->retry_time)
|
||||
return 1;
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void notify_setup(void) {
|
||||
if (!notify_uri || notify_threads <= 0)
|
||||
return;
|
||||
|
||||
notify_threadpool = g_thread_pool_new(do_notify, NULL, notify_threads, false, NULL);
|
||||
|
||||
notify_timers = g_tree_new(notify_req_cmp);
|
||||
pthread_create(¬ify_waiter, NULL, notify_timer, NULL);
|
||||
}
|
||||
|
||||
void notify_cleanup(void) {
|
||||
if (notify_threadpool)
|
||||
g_thread_pool_free(notify_threadpool, true, false);
|
||||
if (notify_waiter && notify_timers) {
|
||||
// get lock, free GTree, signal thread to shut down
|
||||
pthread_mutex_lock(&timer_lock);
|
||||
g_tree_destroy(notify_timers);
|
||||
notify_timers = NULL;
|
||||
pthread_cond_signal(&timer_cond);
|
||||
pthread_mutex_unlock(&timer_lock);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__ ((format (printf, 2, 3)))
|
||||
static void notify_add_header(struct notif_req *req, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
char *s = g_strdup_vprintf(fmt, ap);
|
||||
req->headers = curl_slist_append(req->headers, s);
|
||||
g_free(s);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void notify_push_output(output_t *o, metafile_t *mf, tag_t *tag) {
|
||||
if (!notify_threadpool)
|
||||
return;
|
||||
|
||||
struct notif_req *req = g_slice_alloc0(sizeof(*req));
|
||||
|
||||
req->name = g_strdup(o->file_name);
|
||||
double now = now_double();
|
||||
|
||||
notify_add_header(req, "X-Recording-Call-ID: %s", mf->call_id);
|
||||
notify_add_header(req, "X-Recording-File-Name: %s.%s", o->file_name, o->file_format);
|
||||
notify_add_header(req, "X-Recording-Full-File-Name: %s.%s", o->full_filename, o->file_format);
|
||||
notify_add_header(req, "X-Recording-File-Format: %s", o->file_format);
|
||||
notify_add_header(req, "X-Recording-Kind: %s", o->kind);
|
||||
notify_add_header(req, "X-Recording-Call-Start-Time: %.06f", mf->start_time);
|
||||
notify_add_header(req, "X-Recording-Stream-Start-Time: %.06f", o->start_time);
|
||||
notify_add_header(req, "X-Recording-Call-End-Time: %.06f", now);
|
||||
notify_add_header(req, "X-Recording-Stream-End-Time: %.06f", now);
|
||||
|
||||
if (mf->db_id)
|
||||
notify_add_header(req, "X-Recording-Call-DB-ID: %llu", mf->db_id);
|
||||
if (o->db_id)
|
||||
notify_add_header(req, "X-Recording-Stream-DB-ID: %llu", o->db_id);
|
||||
if (mf->metadata)
|
||||
notify_add_header(req, "X-Recording-Call-Metadata: %s", mf->metadata);
|
||||
if (mf->metadata_db)
|
||||
notify_add_header(req, "X-Recording-DB-Metadata: %s", mf->metadata_db);
|
||||
|
||||
if (tag) {
|
||||
notify_add_header(req, "X-Recording-Tag: %s", tag->name);
|
||||
if (tag->label)
|
||||
notify_add_header(req, "X-Recording-Label: %s", tag->label);
|
||||
if (tag->metadata)
|
||||
notify_add_header(req, "X-Recording-Tag-Metadata: %s", tag->metadata);
|
||||
}
|
||||
|
||||
req->falloff = 5; // initial retry time
|
||||
|
||||
g_thread_pool_push(notify_threadpool, req, NULL);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
#ifndef _NOTIFY_H_
|
||||
#define _NOTIFY_H_
|
||||
|
||||
#include <glib.h>
|
||||
#include "types.h"
|
||||
|
||||
void notify_setup(void);
|
||||
void notify_cleanup(void);
|
||||
|
||||
void notify_push_output(output_t *, metafile_t *, tag_t *);
|
||||
void notify_push_call(metafile_t *);
|
||||
|
||||
#endif
|
Loading…
Reference in new issue