root/src/modules/http.c

Revision 2ea77243f4b484e20c2101cda30ec9fa0e30ea7b, 30.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

Paul Querna's patch plus some fixed warnings.
removal of serf
update of autoconf to find apr (for stomp, sigh)
and detection of libcurl stuffs for the new libcurl based http module

refs #172

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <assert.h>
39 #include <math.h>
40 #include <ctype.h>
41
42 #include <pcre.h>
43
44 #include <libxml/parser.h>
45 #include <libxml/tree.h>
46 #include <libxml/xpath.h>
47 #include <libxml/uri.h>
48
49 #include "noit_module.h"
50 #include "noit_check.h"
51 #include "noit_check_tools.h"
52 #include "utils/noit_log.h"
53 #include "utils/noit_hash.h"
54
55 #include <curl/curl.h>
56
57 #define NOIT_HTTP_VERSION_STRING "0.1"
58
59 typedef struct {
60   noit_hash_table *options;
61   void (*results)(noit_module_t *, noit_check_t *);
62 } http_module_conf_t;
63
64 typedef struct buf_t {
65   char *b;
66   int32_t l;
67 } buf_t;
68
69 /* pqTODO: directly copied from serf_bucket_types.h */
70 #define HTTP_VERSION(major, minor)  ((major) * 1000 + (minor))
71 #define HTTP_11 HTTP_VERSION(1, 1)
72 #define HTTP_10 HTTP_VERSION(1, 0)
73
74 typedef struct {
75   int version;
76   int code;
77   char *reason;
78 } http_status_line;
79
80 typedef struct {
81   struct curl_slist *cheaders;
82   CURL *curl;
83   CURLM *mcurl;
84   int timed_out;
85
86   xmlURIPtr url;
87   buf_t headers;
88   buf_t body;
89
90   struct timeval finish_time;
91   eventer_t fd_event;
92   eventer_t timeout_event;
93   eventer_t process_event;
94   eventer_t done_event;
95
96   noit_module_t *self;
97   noit_check_t *check;
98  
99   http_status_line status;
100   int inside_handler;
101 } http_check_info_t;
102
103 typedef struct {
104   http_check_info_t http;
105   struct timeval xml_doc_time;
106   char *xpathexpr;
107   xmlDocPtr xml_doc;
108   char *resmod;
109   char *resserv;
110 } resmon_check_info_t;
111
112 static noit_log_stream_t nlerr = NULL;
113 static noit_log_stream_t nldeb = NULL;
114 static void http_eventer_free(eventer_t e, http_check_info_t *ci);
115 static int http_consume_messages(http_check_info_t *ci);
116 static void http_log_results(noit_module_t *self, noit_check_t *check);
117 static void resmon_log_results(noit_module_t *self, noit_check_t *check);
118 static void resmon_part_log_results(noit_module_t *self, noit_check_t *check,
119                                     noit_check_t *parent);
120
121 static int http_config(noit_module_t *self, noit_hash_table *options) {
122   http_module_conf_t *conf;
123   conf = noit_module_get_userdata(self);
124   if(conf) {
125     if(conf->options) {
126       noit_hash_destroy(conf->options, free, free);
127       free(conf->options);
128     }
129   }
130   else
131     conf = calloc(1, sizeof(*conf));
132   conf->options = options;
133   conf->results = http_log_results;
134   noit_module_set_userdata(self, conf);
135   return 1;
136 }
137 static int resmon_config(noit_module_t *self, noit_hash_table *options) {
138   http_module_conf_t *conf;
139   conf = noit_module_get_userdata(self);
140   if(conf) {
141     if(conf->options) {
142       noit_hash_destroy(conf->options, free, free);
143       free(conf->options);
144     }
145   }
146   else
147     conf = calloc(1, sizeof(*conf));
148   conf->options = options;
149   if(!conf->options) conf->options = calloc(1, sizeof(*conf->options));
150   noit_hash_store(conf->options, strdup("url"), strlen("url"),
151                   strdup("http://localhost:81/"));
152   conf->results = resmon_log_results;
153   noit_module_set_userdata(self, conf);
154   return 1;
155 }
156
157 static void generic_log_results(noit_module_t *self, noit_check_t *check) {
158   http_module_conf_t *conf;
159   conf = noit_module_get_userdata(self);
160   conf->results(self, check);
161 }
162
163 static void http_log_results(noit_module_t *self, noit_check_t *check) {
164   http_check_info_t *ci = check->closure;
165   struct timeval duration;
166   stats_t current;
167   pcre *expect_code = NULL, *body_match = NULL;
168   u_int32_t duration_ms;
169   void *code_str, *body_str; /* void * for use with hash */
170   char human_buffer[256], code[4], rt[14], bmatch[30];
171   const char *error;
172   int body_matched = 1;
173   int erroffset;
174   int ovector[30];
175
176   noit_check_stats_clear(&current);
177
178   if(!noit_hash_retrieve(check->config, "code", strlen("code"), &code_str)) {
179     code_str = "^200$";
180   }
181   expect_code = pcre_compile((const char *)code_str, 0,
182                              &error, &erroffset, NULL);
183   if(!expect_code)
184     noitL(nlerr, "http code match /%s/ failed @ %d: %s\n", (char *)code_str,
185           erroffset, error);
186
187   if(noit_hash_retrieve(check->config, "body", strlen("body"), &body_str)) {
188     body_match = pcre_compile((const char *)body_str, 0,
189                               &error, &erroffset, NULL);
190     if(!body_match)
191       noitL(nlerr, "http body match /%s/ failed @ %d: %s\n",
192             (char *)body_str, erroffset, error);
193   }
194
195   sub_timeval(ci->finish_time, check->last_fire_time, &duration);
196
197   snprintf(code, sizeof(code), "%3d", ci->status.code);
198   snprintf(rt, sizeof(rt), "%.3fs",
199            (float)duration.tv_sec + (float)duration.tv_usec / 1000000.0);
200
201   bmatch[0] = '\0';
202   if(body_match) {
203     if(pcre_exec(body_match, NULL, ci->body.b, ci->body.l, 0, 0,
204                  ovector, sizeof(ovector)/sizeof(*ovector)) <= 0) {
205       body_matched = 0;
206     }
207     snprintf(bmatch, sizeof(bmatch),
208              ",body=%s", body_matched ? "matched" : "failed");
209   }
210
211   snprintf(human_buffer, sizeof(human_buffer),
212            "code=%s,rt=%s,bytes=%d%s",
213            ci->status.code ? code : "undefined",
214            ci->timed_out ? "timeout" : rt,
215            ci->body.l, bmatch);
216   noitL(nldeb, "http(%s) [%s]\n", check->target, human_buffer);
217
218   memcpy(&current.whence, &ci->finish_time, sizeof(current.whence));
219   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
220   duration_ms = current.duration;
221   current.available = (ci->timed_out || !ci->status.code) ? NP_UNAVAILABLE : NP_AVAILABLE;
222
223   if(body_matched == 0) current.state = NP_BAD;
224   else if(expect_code &&
225           pcre_exec(expect_code, NULL, code, strlen(code), 0, 0,
226                     ovector, sizeof(ovector)/sizeof(*ovector)) > 0)
227     current.state = NP_GOOD;
228   else
229     current.state = NP_BAD;
230
231   current.status = human_buffer;
232   if(current.available == NP_AVAILABLE) {
233     noit_stats_set_metric(&current, "code",
234                           METRIC_STRING, ci->status.code?code:NULL);
235     noit_stats_set_metric(&current, "bytes",
236                           METRIC_INT32, &ci->body.l);
237     noit_stats_set_metric(&current, "duration",
238                           METRIC_UINT32, &duration_ms);
239   }
240   else {
241     noit_stats_set_metric(&current, "code", METRIC_STRING, NULL);
242     noit_stats_set_metric(&current, "bytes", METRIC_INT32, NULL);
243     noit_stats_set_metric(&current, "duration", METRIC_UINT32, NULL);
244   }
245   noit_check_set_stats(self, check, &current);
246   if(expect_code) pcre_free(expect_code);
247   if(body_match) pcre_free(body_match);
248 }
249 static void resmon_part_log_results_xml(noit_module_t *self,
250                                         noit_check_t *check,
251                                         xmlDocPtr xml) {
252   resmon_check_info_t *rci = check->closure;
253   http_check_info_t *ci = &rci->http;
254   xmlXPathContextPtr xpath_ctxt = NULL;
255   stats_t current;
256
257   noit_check_stats_clear(&current);
258   memcpy(&current.whence, &ci->finish_time, sizeof(current.whence));
259   current.available = NP_UNAVAILABLE;
260   current.state = NP_BAD;
261
262   if(xml && rci->xpathexpr) {
263     current.available = NP_AVAILABLE;
264     xpath_ctxt = xmlXPathNewContext(xml);
265     if(xpath_ctxt) {
266       xmlXPathObjectPtr pobj;
267       pobj = xmlXPathEval((xmlChar *)rci->xpathexpr, xpath_ctxt);
268       if(pobj) {
269         int i, cnt;
270         cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
271         for(i=0; i<cnt; i++) {
272           xmlNodePtr node;
273           char *value;
274           node = xmlXPathNodeSetItem(pobj->nodesetval, i);
275           value = (char *)xmlXPathCastNodeToString(node);
276           if(!strcmp((char *)node->name,"last_runtime_seconds")) {
277             float duration = atof(value) * 1000;
278             current.duration = (int) duration;
279           }
280           else if(!strcmp((char *)node->name, "message")) {
281             current.status = strdup(value);
282           }
283           else if(!strcmp((char *)node->name, "state")) {
284             current.state = strcmp(value,"OK") ? NP_BAD : NP_GOOD;
285           }
286           xmlFree(value);
287         }
288         xmlXPathFreeObject(pobj);
289       }
290       xmlXPathFreeContext(xpath_ctxt);
291     }
292   }
293   memcpy(&current.whence, &ci->finish_time, sizeof(current.whence));
294   current.status = current.status ? current.status : strdup("unknown");
295   noitL(nldeb, "resmon_part(%s/%s/%s) [%s]\n", check->target,
296         rci->resmod, rci->resserv, current.status);
297   noit_check_set_stats(self, check, &current);
298   free(current.status);
299 }
300 static void resmon_part_log_results(noit_module_t *self, noit_check_t *check,
301                                     noit_check_t *parent) {
302   resmon_check_info_t *rci = parent->closure;
303   resmon_part_log_results_xml(self, check, rci->xml_doc);
304 }
305 static void resmon_log_results(noit_module_t *self, noit_check_t *check) {
306   resmon_check_info_t *rci = check->closure;
307   http_check_info_t *ci = &rci->http;
308   struct timeval duration;
309   stats_t current;
310   int32_t services = 0;
311   char human_buffer[256], rt[14];
312   xmlDocPtr resmon_results = NULL;
313   xmlXPathContextPtr xpath_ctxt = NULL;
314   xmlXPathObjectPtr pobj = NULL;
315
316   noit_check_stats_clear(&current);
317
318   if(ci->body.b) resmon_results = xmlParseMemory(ci->body.b, ci->body.l);
319   if(resmon_results) {
320     xpath_ctxt = xmlXPathNewContext(resmon_results);
321     pobj = xmlXPathEval((xmlChar *)"/ResmonResults/ResmonResult", xpath_ctxt);
322     if(pobj)
323       if(pobj->type == XPATH_NODESET)
324         services = xmlXPathNodeSetGetLength(pobj->nodesetval);
325   } else {
326     if(ci->body.l)
327       noitL(nlerr, "Error in resmon doc: %s\n", ci->body.b);
328   }
329
330   /* Save our results for future dependent checks */
331   memcpy(&current.whence, &ci->finish_time, sizeof(current.whence));
332   memcpy(&rci->xml_doc_time, &ci->finish_time, sizeof(ci->finish_time));
333   if(rci->xml_doc) xmlFreeDoc(rci->xml_doc);
334   rci->xml_doc = resmon_results;
335
336   if(rci->xpathexpr) {
337     /* This is actually a part check... we had to do all the work as
338      * it isn't being used as a causal firing from a generic resmon check
339      */
340     resmon_part_log_results_xml(self, check, rci->xml_doc);
341     goto out;
342   }
343
344   sub_timeval(ci->finish_time, check->last_fire_time, &duration);
345   snprintf(rt, sizeof(rt), "%.3fs",
346            (float)duration.tv_sec + (float)duration.tv_usec / 1000000.0);
347   snprintf(human_buffer, sizeof(human_buffer),
348            "services=%d,rt=%s",
349            services,
350            ci->timed_out ? "timeout" : rt);
351   noitL(nldeb, "resmon(%s) [%s]\n", check->target, human_buffer);
352
353   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
354   current.available = (ci->timed_out || ci->status.code != 200) ?
355                           NP_UNAVAILABLE : NP_AVAILABLE;
356   current.state = services ? NP_GOOD : NP_BAD;
357   current.status = human_buffer;
358
359   noit_stats_set_metric(&current, "services", METRIC_INT32, &services);
360   if(services) {
361     int i;
362     for(i=0; i<services; i++) {
363       xmlNodePtr node, attrnode;
364       node = xmlXPathNodeSetItem(pobj->nodesetval, i);
365       if(node) {
366         int a;
367         char *attrs[3] = { "last_runtime_seconds", "state", "message" };
368         char *resmod = NULL, *resserv = NULL, *value = NULL;
369         char attr[1024];
370         xmlXPathObjectPtr sobj;
371
372         xpath_ctxt->node = node;
373         sobj = xmlXPathEval((xmlChar *)"@module", xpath_ctxt);
374         if(sobj) {
375           resmod = (char *)xmlXPathCastNodeSetToString(sobj->nodesetval);
376           xmlXPathFreeObject(sobj);
377         }
378         sobj = xmlXPathEval((xmlChar *)"@service", xpath_ctxt);
379         if(sobj) {
380           resserv = (char *)xmlXPathCastNodeSetToString(sobj->nodesetval);
381           xmlXPathFreeObject(sobj);
382         }
383         if(!resmod && !resserv) continue;
384
385         for(a=0; a<3; a++) {
386           int32_t intval;
387           sobj = xmlXPathEval((xmlChar *)attrs[a], xpath_ctxt);
388           attrnode = xmlXPathNodeSetItem(sobj->nodesetval, 0);
389           value = (char *)xmlXPathCastNodeToString(attrnode);
390           xmlXPathFreeObject(sobj);
391           snprintf(attr, sizeof(attr), "%s`%s`%s",
392                    resmod, resserv, (char *)attrnode->name);
393           switch(a) {
394             case 0:
395               /* The first is integer */
396               intval = (int)(atof(value) * 1000.0);
397               noit_stats_set_metric(&current, attr, METRIC_INT32, &intval);
398               break;
399             case 1:
400               noit_stats_set_metric(&current, attr, METRIC_STRING, value);
401               break;
402             case 2:
403               noit_stats_set_metric(&current, attr, METRIC_GUESS, value);
404               break;
405           }
406           xmlFree(value);
407         }
408         if(resmod) xmlFree(resmod);
409         if(resserv) xmlFree(resserv);
410       }
411     }
412   }
413
414   noit_check_set_stats(self, check, &current);
415
416  out:
417   if(pobj) xmlXPathFreeObject(pobj);
418   if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
419 }
420
421 static void http_cleanup_check(noit_module_t *self, noit_check_t *check) {
422   http_check_info_t *ci;
423   ci = check->closure;
424   noitL(nldeb, "http_cleanup_check(%p)\n", ci);
425   if (ci->curl) {
426     if (ci->mcurl) {
427       curl_multi_remove_handle(ci->mcurl, ci->curl);
428     }
429     curl_easy_cleanup(ci->curl);
430     ci->curl = NULL;
431   }
432
433   if (ci->mcurl) {
434     curl_multi_cleanup(ci->mcurl);
435     ci->mcurl = NULL;
436   }
437
438   if (ci->cheaders) {
439     curl_slist_free_all(ci->cheaders);
440     ci->cheaders = NULL;
441   }
442
443   if(ci->fd_event) {
444     http_eventer_free(eventer_remove_fd(ci->fd_event->fd), ci);
445     ci->fd_event = NULL;
446   }
447
448   if(ci->process_event) {
449     http_eventer_free(eventer_remove_recurrent(ci->process_event), ci);
450     ci->process_event = NULL;
451   }
452  
453   if (ci->status.reason) {
454     free(ci->status.reason);
455     ci->status.reason = NULL;
456   }
457
458   ci->check->flags &= ~NP_RUNNING;
459   ci->timeout_event = NULL;
460   free(ci->body.b);
461   free(ci->headers.b);
462   memset(ci, 0, sizeof(*ci));
463 }
464
465
466 static int http_init(noit_module_t *self) {
467   return 0;
468 }
469
470 static void append_buf(buf_t *b,
471                        const char *data, int len) {
472   //noitL(nldeb, "append_buf(%p, %d)\n", b, len);
473   b->b = realloc(b->l == 0 ? NULL : b->b, b->l + len + 1);
474   memcpy(b->b + b->l, data, len);
475   b->l += len;
476   b->b[b->l] = '\0';
477 }
478
479 #define BODY_MAX_SIZE 1024 * 512
480
481 static size_t http_write_data( void *ptr, size_t size, size_t nmemb, void *baton)
482 {
483   http_check_info_t *ci = baton;
484   size_t len =  size * nmemb;
485   size_t used = MIN(BODY_MAX_SIZE - ci->body.l, len);
486   noitL(nldeb, "http_write_data(%p, %d)\n", ci, (int)used);
487   if (used != 0) {
488     append_buf(&ci->body, ptr, used);
489   }
490   return used;
491 }
492
493
494 static size_t http_write_headers( void *ptr, size_t size, size_t nmemb, void *baton)
495 {
496   http_check_info_t *ci = baton;
497   size_t len =  size * nmemb;
498   size_t used = MIN(BODY_MAX_SIZE - ci->headers.l, len);
499   noitL(nldeb, "http_write_headers(%p, %d)\n", ci, (int)used);
500   if (used != 0) {
501     append_buf(&ci->headers, ptr, used);
502   }
503
504   if (ci->status.code == 0) {
505       /* HTTP/1.1 200 OK */
506     if (ci->headers.l > strlen("HTTP/#.# ###")) {
507       char *p = NULL;
508       ci->status.version = HTTP_VERSION(ci->headers.b[5] - '0',  ci->headers.b[7] - '0');
509       if (ci->status.version != HTTP_11 && ci->status.version != HTTP_10) {
510         /* TODO: log error*/
511         noitL(nldeb, "http_write_headers(%p) -- Invalid HTTP Version: %d\n", ci, ci->status.version);
512         return 0;
513       }
514      
515       ci->status.code = strtol(ci->headers.b + 8, &p, 10);
516       if (ci->status.code != 0 && p) {
517         while (*p && isspace(*p)) p++;
518         if (*p) {
519           ci->status.reason = strdup(p);
520         }
521       }
522     }
523   }
524   return used;
525 }
526
527 static int http_handler(eventer_t e, int mask,
528                         void *closure, struct timeval *now)
529 {
530   int cmask = 0;
531   int handles = 0;
532   http_check_info_t *ci = closure;
533
534   if (!ci->curl) {
535     return 0;
536   }
537
538   if(mask & EVENTER_READ) cmask |= CURL_CSELECT_IN;
539   if(mask & EVENTER_WRITE) cmask|= CURL_CSELECT_OUT;
540   if(mask & EVENTER_EXCEPTION) cmask |= CURL_CSELECT_ERR;
541  
542   ci->inside_handler = 1;
543   //noitL(nldeb, "http_handler(%p, emask=%d, cmask=%d)\n", ci, mask, cmask);
544   curl_multi_socket_action(ci->mcurl, e->fd, cmask, &handles);
545   ci->inside_handler = 0;
546
547   return e->mask;
548 }
549
550 static int http_socket_cb(CURL *_curl, curl_socket_t fd, int action,
551                           void *userp,
552                           void *socketp)
553 {
554   eventer_t e;
555   http_check_info_t *ci =  (http_check_info_t *)userp;
556
557   e = eventer_find_fd(fd);
558   switch (action) {
559     case CURL_POLL_INOUT:
560     case CURL_POLL_OUT:
561     case CURL_POLL_IN:
562       if(!e) {
563         ci->fd_event = e = eventer_alloc();
564         e->fd = fd;
565         e->callback = http_handler;
566         e->closure = ci;
567       }
568
569       /* curl API don't have a command to look for err, but it actually
570        * does want to know if there is one!
571        */
572       e->mask |= EVENTER_EXCEPTION;
573       if (action == CURL_POLL_INOUT) e->mask |= EVENTER_READ|EVENTER_WRITE;
574       if (action == CURL_POLL_OUT) e->mask |= EVENTER_WRITE;
575       if (action == CURL_POLL_IN) e->mask |= EVENTER_READ;
576       noitL(nldeb, "http_socket_cb(add) => %d, %x [%c%c%c]\n",
577             fd, action,
578             (e->mask & EVENTER_READ) ? 'I' : '-',
579             (e->mask & EVENTER_WRITE) ? 'O' : '-',
580             (e->mask & EVENTER_EXCEPTION) ? 'E' : '-');
581       eventer_add(e);
582       break;
583     case CURL_POLL_REMOVE:
584       noitL(nldeb, "http_socket_cb(remove) => %d\n", fd);
585       if (e) {
586         e->closure = NULL;
587         e->mask = 0;
588         if (ci->fd_event) {
589           eventer_t te = eventer_remove_fd(ci->fd_event->fd);
590           if (te && ci->inside_handler == 0) {
591             http_eventer_free(te, ci);
592           }
593           else {
594             te->mask = 0;
595           }
596           ci->fd_event = NULL;
597         }
598       }
599       break;
600     default:
601       abort();
602   }
603
604   return 0;
605 }
606
607
608 static int http_all_done(eventer_t e, int mask,
609                           void *closure, struct timeval *now)
610 {
611   http_check_info_t *ci = closure;
612   noitL(nldeb, "http_all_done(%p)\n", ci);
613   generic_log_results(ci->self, ci->check);
614   http_cleanup_check(ci->self, ci->check);
615   return 0;
616 }
617
618 static int http_consume_messages(http_check_info_t *ci)
619 {
620   CURLMsg *msg;
621   int count;
622
623   while ((msg = curl_multi_info_read(ci->mcurl, &count)) != NULL) {
624     if (msg->msg == CURLMSG_DONE) {
625       struct timeval when;
626       long ret;
627
628       noitL(nldeb, "http_consume_messages(%p) => DONE\n", ci);
629       gettimeofday(&when, NULL);
630       memcpy(&ci->finish_time, &when, sizeof(when));
631       curl_easy_getinfo(ci->curl, CURLINFO_RESPONSE_CODE, &ret);
632       if (ret == 0) {
633         /* no HTTP transfer took place! */
634         ci->timed_out = 1;
635       }
636       else {
637         ci->timed_out = 0;
638       }
639
640       ci->done_event = eventer_alloc();;
641       ci->done_event->mask = EVENTER_TIMER;
642       memcpy(&ci->done_event->whence, &when, sizeof(when));
643       ci->done_event->closure = ci;
644       ci->done_event->callback = http_all_done;
645       eventer_add(ci->done_event);
646     }
647   }
648
649   return 0;
650 }
651
652
653 static int http_recurrent(eventer_t e, int mask,
654                           void *closure, struct timeval *now)
655 {
656   http_check_info_t *ci = closure;
657   http_consume_messages(ci);
658   return e->mask;
659 }
660
661 static int http_timeout(eventer_t e, int mask,
662                          void *closure, struct timeval *now)
663 {
664   int cc;
665   int handles = 0;
666   http_check_info_t *ci = closure;
667
668   if (ci->mcurl == NULL) {
669     ci->timeout_event = NULL;
670     return 0;
671   }
672  
673   noitL(nldeb, "http_timeout(%p)\n", ci);
674
675   do {
676     cc = curl_multi_socket(ci->mcurl, CURL_SOCKET_TIMEOUT, &handles);
677   } while(cc == CURLM_CALL_MULTI_PERFORM && handles != 0);
678  
679   ci->timeout_event = NULL;
680   return 0;
681
682
683 static int http_set_timeout_cb(CURLM *_curlm, long timeoutms, void *closure)
684 {
685   struct timeval when, p_int;
686   http_check_info_t *ci = closure;
687
688   noitL(nldeb, "http_set_timeout_cb(%p, %d)\n", ci, (int)timeoutms);
689  
690   if (ci->timeout_event != NULL) {
691     http_eventer_free(eventer_remove(ci->timeout_event), ci);
692     ci->timeout_event = NULL;
693   }
694
695   ci->timeout_event = eventer_alloc();
696   ci->timeout_event->mask = EVENTER_TIMER;
697   gettimeofday(&when, NULL);
698   p_int.tv_sec = timeoutms / 1000;
699   p_int.tv_usec = (timeoutms % 1000) * 1000;
700   add_timeval(when, p_int, &ci->timeout_event->whence);
701   ci->timeout_event->closure = ci;
702   ci->timeout_event->callback = http_timeout;
703   eventer_add(ci->timeout_event);
704  
705   return 0;
706 }
707
708 static int http_initiate(noit_module_t *self, noit_check_t *check) {
709   char buf[1024];
710   struct timeval when;
711   http_check_info_t *ci;
712   http_module_conf_t *mod_config;
713   void *config_url;
714   char *urlstr;
715
716   mod_config = noit_module_get_userdata(self);
717   ci = (http_check_info_t *)check->closure;
718   /* We cannot be running */
719   if (check->flags & NP_RUNNING) {
720     generic_log_results(ci->self, ci->check);
721     http_cleanup_check(ci->self, ci->check);
722     assert(!(check->flags & NP_RUNNING));
723   }
724   check->flags |= NP_RUNNING;
725
726   /* remove a timeout if we still have one -- we should unless someone
727    * has set a lower timeout than the period.
728    */
729   ci->timed_out = 1;
730   if(ci->timeout_event) {
731     http_eventer_free(eventer_remove(ci->timeout_event), ci);
732     ci->timeout_event = NULL;
733   }
734
735   gettimeofday(&when, NULL);
736   memcpy(&check->last_fire_time, &when, sizeof(when));
737   ci->finish_time.tv_sec = ci->finish_time.tv_usec = 0L;
738
739   ci->self = self;
740   ci->check = check;
741
742   if(!noit_hash_retrieve(check->config, "url", strlen("url"), &config_url)) {
743     if(!mod_config->options ||
744        !noit_hash_retrieve(mod_config->options, "url", strlen("url"),
745                            &config_url)) {
746       config_url = "http://localhost/";
747     }
748   }
749
750   ci->url = xmlParseURI(config_url);
751
752   if (!ci->url->scheme) {
753     ci->url->scheme = strdup("http");
754   }
755
756   if (!ci->url->port) {
757     if (strcmp("http", ci->url->scheme) == 0) {
758       ci->url->port = 80;
759     }
760     else if (strcmp("https", ci->url->scheme) == 0) {
761       ci->url->port = 443;
762     }
763     else {
764       ci->url->port = 80;
765     }
766   }
767
768   if (!ci->url->path) {
769     ci->url->path = strdup("/");
770   }
771
772   if (strcasecmp(ci->url->scheme, "https") == 0) {
773 #if 0
774     /* TODO: Custom CA validation */
775     void *vstr;
776     http_module_conf_t *conf;
777     conf = noit_module_get_userdata(self);
778     if(noit_hash_retrieve(check->config, "ca_chain",
779                           strlen("ca_chain"), &vstr))
780       ci->app_ctx.ca_chain_file = apr_pstrdup(ci->pool, vstr);
781     else if(noit_hash_retrieve(conf->options, "ca_chain",
782                                strlen("ca_chain"), &vstr))
783       ci->app_ctx.ca_chain_file = apr_pstrdup(ci->pool, vstr);
784
785     if(noit_hash_retrieve(check->config, "certificate_file",
786                           strlen("certificate_file"), &vstr))
787       ci->app_ctx.certificate_file = apr_pstrdup(ci->pool, vstr);
788     else if(noit_hash_retrieve(conf->options, "certificate_file",
789                                strlen("certificate_file"), &vstr))
790       ci->app_ctx.certificate_file = apr_pstrdup(ci->pool, vstr);
791 #endif
792   }
793
794   snprintf(buf, sizeof(buf), "Host: %s", ci->url->server);
795   ci->cheaders = curl_slist_append(ci->cheaders, buf);
796   ci->cheaders = curl_slist_append(ci->cheaders,   "Accept-Encoding: deflate,gzip");
797
798   ci->curl = curl_easy_init();
799
800   curl_easy_setopt(ci->curl, CURLOPT_NOSIGNAL, 0);
801   curl_easy_setopt(ci->curl, CURLOPT_WRITEFUNCTION, http_write_data);
802   curl_easy_setopt(ci->curl, CURLOPT_WRITEDATA, ci);
803   curl_easy_setopt(ci->curl, CURLOPT_HEADERFUNCTION, http_write_headers);
804   curl_easy_setopt(ci->curl, CURLOPT_HEADERDATA, ci);
805
806   free(ci->url->server);
807   ci->url->server = strdup(check->target);
808   urlstr = (char *)xmlSaveUri(ci->url);
809   curl_easy_setopt(ci->curl, CURLOPT_URL, urlstr);
810   noitL(nldeb, "http_initiate(%p,%s,url=%s)\n",
811         ci, check->target, urlstr);
812   xmlFreeURI(ci->url);
813   free(urlstr);
814   ci->url = NULL;
815
816
817   curl_easy_setopt(ci->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
818   curl_easy_setopt(ci->curl, CURLOPT_FOLLOWLOCATION, 0);
819   curl_easy_setopt(ci->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
820
821   curl_easy_setopt(ci->curl, CURLOPT_USERAGENT, "Noit/" NOIT_HTTP_VERSION_STRING);
822   curl_easy_setopt(ci->curl, CURLOPT_HTTPHEADER, ci->cheaders);
823
824   curl_easy_setopt(ci->curl, CURLOPT_FRESH_CONNECT, 1);
825   curl_easy_setopt(ci->curl, CURLOPT_FORBID_REUSE, 1);
826
827   /* TODO: more SSL options */
828   curl_easy_setopt(ci->curl, CURLOPT_SSL_VERIFYPEER, 0);
829   curl_easy_setopt(ci->curl, CURLOPT_SSL_VERIFYHOST, 0);
830   curl_easy_setopt(ci->curl, CURLOPT_TIMEOUT_MS, check->timeout);
831   curl_easy_setopt(ci->curl, CURLOPT_CONNECTTIMEOUT_MS, check->timeout);
832
833   /* TODO: Consider re-using the multi-init */
834   ci->mcurl = curl_multi_init();
835   curl_multi_setopt(ci->mcurl, CURLMOPT_SOCKETFUNCTION, http_socket_cb);
836   curl_multi_setopt(ci->mcurl, CURLMOPT_SOCKETDATA, ci);
837   curl_multi_setopt(ci->mcurl, CURLMOPT_PIPELINING, 0);
838  
839   curl_multi_setopt(ci->mcurl, CURLMOPT_TIMERFUNCTION, http_set_timeout_cb);
840   curl_multi_setopt(ci->mcurl, CURLMOPT_TIMERDATA, ci);
841
842   curl_multi_add_handle(ci->mcurl, ci->curl);
843
844   {
845     int running_handles = 0;
846     while (curl_multi_perform(ci->mcurl, &running_handles) == CURLM_CALL_MULTI_PERFORM);
847   }
848
849   ci->process_event = eventer_alloc();
850   ci->process_event->closure = ci;
851   ci->process_event->mask = EVENTER_RECURRENT;
852   ci->process_event->callback = http_recurrent;
853
854   eventer_add_recurrent(ci->process_event);
855
856   return 0;
857 }
858 static int http_initiate_check(noit_module_t *self, noit_check_t *check,
859                                int once, noit_check_t *cause) {
860   if(!check->closure) check->closure = calloc(1, sizeof(http_check_info_t));
861   INITIATE_CHECK(http_initiate, self, check);
862   return 0;
863 }
864 static int resmon_initiate_check(noit_module_t *self, noit_check_t *check,
865                                  int once, noit_check_t *parent) {
866   /* resmon_check_info_t gives us a bit more space */
867   if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t));
868   INITIATE_CHECK(http_initiate, self, check);
869   return 0;
870 }
871
872 static void resmon_cleanup_check(noit_module_t *self, noit_check_t *check) {
873   resmon_check_info_t *rci;
874   rci = check->closure;
875   if(rci) {
876     if(rci->xpathexpr) free(rci->xpathexpr);
877     if(rci->resmod) free(rci->resmod);
878     if(rci->resserv) free(rci->resserv);
879     if(rci->xml_doc) xmlFreeDoc(rci->xml_doc);
880     http_cleanup_check(self, check);
881     memset(rci, 0, sizeof(*rci));
882   }
883 }
884 static int resmon_part_initiate_check(noit_module_t *self, noit_check_t *check,
885                                       int once, noit_check_t *parent) {
886   char xpathexpr[1024];
887   void *resmod, *resserv;
888   resmon_check_info_t *rci;
889
890   if(NOIT_CHECK_DISABLED(check) || NOIT_CHECK_KILLED(check)) return 0;
891
892   if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t));
893   rci = check->closure;
894   if(!rci->xpathexpr) {
895     if(!noit_hash_retrieve(check->config,
896                            "resmon_module", strlen("resmon_module"),
897                            &resmod)) {
898       resmod = "DUMMY_MODULE";
899     }
900     if(!noit_hash_retrieve(check->config,
901                            "resmon_service", strlen("resmon_service"),
902                            &resserv)) {
903       resserv = "DUMMY_SERVICE";
904     }
905     snprintf(xpathexpr, sizeof(xpathexpr),
906              "//ResmonResult[@module=\"%s\" and @service=\"%s\"]/*",
907              (const char *)resmod, (const char *)resserv);
908     rci->xpathexpr = strdup(xpathexpr);
909     rci->resmod = strdup(resmod);
910     rci->resserv = strdup(resserv);
911   }
912
913   if(parent && !strcmp(parent->module, "resmon")) {
914     /* Content is cached in the parent */
915     http_check_info_t *ci = &rci->http;
916     gettimeofday(&ci->finish_time, NULL);
917     resmon_part_log_results(self, check, parent);
918     return 0;
919   }
920   INITIATE_CHECK(http_initiate, self, check);
921   return 0;
922 }
923
924 static void http_eventer_free(eventer_t e, http_check_info_t *ci)
925 {
926   noitL(nldeb, "http_eventer_free(%p)\n", e);
927   if (!e)
928     return;
929   eventer_free(e);
930   return;
931 }
932
933
934 static int http_onload(noit_image_t *self) {
935   curl_global_init(CURL_GLOBAL_ALL);
936   atexit(curl_global_cleanup);
937  
938   nlerr = noit_log_stream_find("error/http");
939   nldeb = noit_log_stream_find("debug/http");
940   if(!nlerr) nlerr = noit_stderr;
941   if(!nldeb) nldeb = noit_debug;
942
943   eventer_name_callback("http/http_handler", http_handler);
944   eventer_name_callback("http/http_timeout", http_timeout);
945   eventer_name_callback("http/http_all_done", http_all_done);
946   eventer_name_callback("http/http_recurrent", http_recurrent);
947   return 0;
948 }
949
950 #include "http.xmlh"
951 noit_module_t http = {
952   {
953     NOIT_MODULE_MAGIC,
954     NOIT_MODULE_ABI_VERSION,
955     "http",
956     "libcurl-based HTTP and HTTPS resource checker",
957     http_xml_description,
958     http_onload
959   },
960   http_config,
961   http_init,
962   http_initiate_check,
963   http_cleanup_check
964 };
965
966 #include "resmon.xmlh"
967 noit_module_t resmon = {
968   {
969     NOIT_MODULE_MAGIC,
970     NOIT_MODULE_ABI_VERSION,
971     "resmon",
972     "libserf-based resmon resource checker",
973     resmon_xml_description,
974     http_onload
975   },
976   resmon_config,
977   http_init,
978   resmon_initiate_check,
979   resmon_cleanup_check
980 };
981
982 #include "resmon_part.xmlh"
983 noit_module_t resmon_part = {
984   {
985     NOIT_MODULE_MAGIC,
986     NOIT_MODULE_ABI_VERSION,
987     "resmon_part",
988     "resmon part resource checker",
989     resmon_part_xml_description,
990     http_onload
991   },
992   resmon_config,
993   http_init,
994   resmon_part_initiate_check,
995   resmon_cleanup_check
996 };
997
Note: See TracBrowser for help on using the browser.