http plugin: http_write: Clean-up.
[collectd.git] / src / http.c
1 /**
2  * collectd - src/http.c
3  * Copyright (C) 2007-2009  Florian octo Forster
4  * Copyright (C) 2009       Doug MacEachern
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  *   Doug MacEachern <dougm@hyperic.com>
22  **/
23
24 #include "collectd.h"
25 #include "plugin.h"
26 #include "common.h"
27 #include "utils_cache.h"
28 #include "utils_parse_option.h"
29
30 #if HAVE_PTHREAD_H
31 # include <pthread.h>
32 #endif
33
34 #include <curl/curl.h>
35
36 /*
37  * Private variables
38  */
39 static const char *config_keys[] =
40 {
41         "URL", "User", "Password"
42 };
43 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
44
45 static char *location   = NULL;
46
47 char *user;
48 char *pass;
49 char *credentials;
50
51 CURL *curl;
52 char curl_errbuf[CURL_ERROR_SIZE];
53
54 #define SEND_BUFFER_SIZE 4096
55 static char   send_buffer[SEND_BUFFER_SIZE];
56 static size_t send_buffer_free;
57 static size_t send_buffer_fill;
58
59 static pthread_mutex_t  send_lock = PTHREAD_MUTEX_INITIALIZER;
60
61 static int http_init(void) /* {{{ */
62 {
63
64         curl = curl_easy_init ();
65
66         if (curl == NULL)
67         {
68                 ERROR ("curl plugin: curl_easy_init failed.");
69                 return (-1);
70         }
71
72         struct curl_slist *headers=NULL;
73
74         curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
75
76         headers = curl_slist_append(headers, "Accept:  */*");
77         headers = curl_slist_append(headers, "Content-Type: text/plain");
78         curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers);
79
80         curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, curl_errbuf);
81         curl_easy_setopt (curl, CURLOPT_URL, location);
82
83         if (user != NULL)
84         {
85                 size_t credentials_size;
86
87                 credentials_size = strlen (user) + 2;
88                 if (pass != NULL)
89                         credentials_size += strlen (pass);
90
91                 credentials = (char *) malloc (credentials_size);
92                 if (credentials == NULL)
93                 {
94                         ERROR ("curl plugin: malloc failed.");
95                         return (-1);
96                 }
97
98                 ssnprintf (credentials, credentials_size, "%s:%s",
99                                 user, (pass == NULL) ? "" : pass);
100                 curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
101                 curl_easy_setopt (curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
102         }
103
104         return (0);
105 } /* }}} */
106
107 static int http_value_list_to_string (char *buffer, /* {{{ */
108                 size_t buffer_size,
109                 const data_set_t *ds, const value_list_t *vl)
110 {
111         size_t offset = 0;
112         int status;
113         int i;
114
115         assert (0 == strcmp (ds->type, vl->type));
116
117         memset (buffer, 0, buffer_size);
118
119 #define BUFFER_ADD(...) do { \
120         status = ssnprintf (buffer + offset, buffer_size - offset, \
121                         __VA_ARGS__); \
122         if (status < 1) \
123                 return (-1); \
124         else if (((size_t) status) >= (buffer_size - offset)) \
125                 return (-1); \
126         else \
127                 offset += ((size_t) status); \
128 } while (0)
129
130         BUFFER_ADD ("%lu", (unsigned long) vl->time);
131
132         for (i = 0; i < ds->ds_num; i++)
133 {
134         if (ds->ds[i].type == DS_TYPE_GAUGE)
135                 BUFFER_ADD (":%f", vl->values[i].gauge);
136         else if (ds->ds[i].type == DS_TYPE_COUNTER)
137                 BUFFER_ADD (":%llu", vl->values[i].counter);
138         else if (ds->ds[i].type == DS_TYPE_DERIVE)
139                 BUFFER_ADD (":%"PRIi64, vl->values[i].derive);
140         else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
141                 BUFFER_ADD (":%"PRIu64, vl->values[i].absolute);
142         else
143         {
144                 ERROR ("http plugin: Unknown data source type: %i",
145                                 ds->ds[i].type);
146                 return (-1);
147         }
148 } /* for ds->ds_num */
149
150 #undef BUFFER_ADD
151
152 return (0);
153 } /* }}} int http_value_list_to_string */
154
155 static int http_config (const char *key, const char *value) /* {{{ */
156 {
157         if (strcasecmp ("URL", key) == 0)
158         {
159                 if (location != NULL)
160                         free (location);
161                 location = strdup (value);
162                 if (location != NULL)
163                 {
164                         int len = strlen (location);
165                         while ((len > 0) && (location[len - 1] == '/'))
166                         {
167                                 len--;
168                                 location[len] = '\0';
169                         }
170                         if (len <= 0)
171                         {
172                                 free (location);
173                                 location = NULL;
174                         }
175                 }
176         }
177         else if (strcasecmp ("User", key) == 0)
178         {
179                 if (user != NULL)
180                         free (user);
181                 user = strdup (value);
182                 if (user != NULL)
183                 {
184                         int len = strlen (user);
185                         while ((len > 0) && (user[len - 1] == '/'))
186                         {
187                                 len--;
188                                 user[len] = '\0';
189                         }
190                         if (len <= 0)
191                         {
192                                 free (user);
193                                 user = NULL;
194                         }
195                 }
196         }
197         else if (strcasecmp ("Password", key) == 0)
198         {
199                 if (pass != NULL)
200                         free (pass);
201                 pass = strdup (value);
202                 if (pass != NULL)
203                 {
204                         int len = strlen (pass);
205                         while ((len > 0) && (pass[len - 1] == '/'))
206                         {
207                                 len--;
208                                 pass[len] = '\0';
209                         }
210                         if (len <= 0)
211                         {
212                                 free (pass);
213                                 pass = NULL;
214                         }
215                 }
216         }
217         else
218         {
219                 return (-1);
220         }
221         return (0);
222 } /* }}} int http_config */
223
224 static void http_init_buffer (void)  /* {{{ */
225 {
226         memset (send_buffer, 0, sizeof (send_buffer));
227         send_buffer_free = sizeof (send_buffer);
228         send_buffer_fill = 0;
229 } /* }}} http_init_buffer */
230
231 static int http_send_buffer (char *buffer) /* {{{ */
232 {
233         int status = 0;
234
235         curl_easy_setopt (curl, CURLOPT_POSTFIELDS, buffer);
236         status = curl_easy_perform (curl);
237         if (status != 0)
238         {
239                 ERROR ("http plugin: curl_easy_perform failed with staus %i: %s",
240                                 status, curl_errbuf);
241         }
242         return (status);
243 } /* }}} http_send_buffer */
244
245 static int http_flush_buffer (void) /* {{{ */
246 {
247         int status = 0;
248         DEBUG ("http plugin: flushing buffer:\n%s", send_buffer);
249
250         status = http_send_buffer (send_buffer);
251         http_init_buffer ();
252
253         return (status);
254 } /* }}} http_flush_buffer */
255
256 static int http_write_command (const data_set_t *ds, const value_list_t *vl) /* {{{ */
257 {
258         char key[10*DATA_MAX_NAME_LEN];
259         char values[512];
260         char command[1024];
261         size_t command_len;
262
263         int status;
264
265         if (0 != strcmp (ds->type, vl->type)) {
266                 ERROR ("http plugin: DS type does not match value list type");
267                 return -1;
268         }
269
270         /* Copy the identifier to `key' and escape it. */
271         status = FORMAT_VL (key, sizeof (key), vl);
272         if (status != 0) {
273                 ERROR ("http plugin: error with format_name");
274                 return (status);
275         }
276         escape_string (key, sizeof (key));
277
278         /* Convert the values to an ASCII representation and put that into
279          * `values'. */
280         status = http_value_list_to_string (values, sizeof (values), ds, vl);
281         if (status != 0) {
282                 ERROR ("http plugin: error with http_value_list_to_string");
283                 return (status);
284         }
285
286         command_len = (size_t) ssnprintf (command, sizeof (command),
287                         "PUTVAL %s interval=%i %s\n",
288                         key, vl->interval, values);
289         if (command_len >= sizeof (command)) {
290                 ERROR ("http plugin: Command buffer too small: "
291                                 "Need %zu bytes.", command_len + 1);
292                 return (-1);
293         }
294
295         pthread_mutex_lock (&send_lock);
296
297         /* Check if we have enough space for this command. */
298         if (command_len >= send_buffer_free)
299         {
300                 status = http_flush_buffer();
301                 if (status != 0)
302                 {
303                         pthread_mutex_unlock (&send_lock);
304                         return status;
305                 }
306         }
307         assert (command_len < send_buffer_free);
308
309         /* `command_len + 1' because `command_len' does not include the
310          * trailing null byte. Neither does `send_buffer_fill'. */
311         memcpy (send_buffer + send_buffer_fill, command, command_len + 1);
312         send_buffer_fill += command_len;
313         send_buffer_free -= command_len;
314
315         pthread_mutex_unlock (&send_lock);
316
317         return (0);
318 } /* }}} int http_write_command */
319
320 static int http_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
321                 user_data_t __attribute__((unused)) *user_data)
322 {
323         int status;
324
325         status = http_write_command (ds, vl);
326
327         return (status);
328 } /* }}} int http_write */
329
330 static int http_shutdown (void) /* {{{ */
331 {
332         http_flush_buffer();
333         curl_easy_cleanup(curl);
334         return (0);
335 }
336
337 void module_register (void) /* {{{ */
338 {
339         plugin_register_init("http", http_init);
340         plugin_register_config ("http", http_config,
341                         config_keys, config_keys_num);
342         plugin_register_write ("http", http_write, /* user_data = */ NULL);
343         plugin_register_shutdown("http", http_shutdown);
344 } /* }}} void module_register */
345
346 /* vim: set fdm=marker sw=8 ts=8 tw=78 et : */