root/src/modules/http.c

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

don't use it, if you don't have it.

  • 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 #ifdef CURLOPT_PROTOCOLS
818   curl_easy_setopt(ci->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
819 #endif
820   curl_easy_setopt(ci->curl, CURLOPT_FOLLOWLOCATION, 0);
821
822   curl_easy_setopt(ci->curl, CURLOPT_USERAGENT, "Noit/" NOIT_HTTP_VERSION_STRING);
823   curl_easy_setopt(ci->curl, CURLOPT_HTTPHEADER, ci->cheaders);
824
825   curl_easy_setopt(ci->curl, CURLOPT_FRESH_CONNECT, 1);
826   curl_easy_setopt(ci->curl, CURLOPT_FORBID_REUSE, 1);
827
828   /* TODO: more SSL options */
829   curl_easy_setopt(ci->curl, CURLOPT_SSL_VERIFYPEER, 0);
830   curl_easy_setopt(ci->curl, CURLOPT_SSL_VERIFYHOST, 0);
831   curl_easy_setopt(ci->curl, CURLOPT_TIMEOUT_MS, check->timeout);
832   curl_easy_setopt(ci->curl, CURLOPT_CONNECTTIMEOUT_MS, check->timeout);
833
834   /* TODO: Consider re-using the multi-init */
835   ci->mcurl = curl_multi_init();
836   curl_multi_setopt(ci->mcurl, CURLMOPT_SOCKETFUNCTION, http_socket_cb);
837   curl_multi_setopt(ci->mcurl, CURLMOPT_SOCKETDATA, ci);
838   curl_multi_setopt(ci->mcurl, CURLMOPT_PIPELINING, 0);
839  
840   curl_multi_setopt(ci->mcurl, CURLMOPT_TIMERFUNCTION, http_set_timeout_cb);
841   curl_multi_setopt(ci->mcurl, CURLMOPT_TIMERDATA, ci);
842
843   curl_multi_add_handle(ci->mcurl, ci->curl);
844
845   {
846     int running_handles = 0;
847     while (curl_multi_perform(ci->mcurl, &running_handles) == CURLM_CALL_MULTI_PERFORM);
848   }
849
850   ci->process_event = eventer_alloc();
851   ci->process_event->closure = ci;
852   ci->process_event->mask = EVENTER_RECURRENT;
853   ci->process_event->callback = http_recurrent;
854
855   eventer_add_recurrent(ci->process_event);
856
857   return 0;
858 }
859 static int http_initiate_check(noit_module_t *self, noit_check_t *check,
860                                int once, noit_check_t *cause) {
861   if(!check->closure) check->closure = calloc(1, sizeof(http_check_info_t));
862   INITIATE_CHECK(http_initiate, self, check);
863   return 0;
864 }
865 static int resmon_initiate_check(noit_module_t *self, noit_check_t *check,
866                                  int once, noit_check_t *parent) {
867   /* resmon_check_info_t gives us a bit more space */
868   if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t));
869   INITIATE_CHECK(http_initiate, self, check);
870   return 0;
871 }
872
873 static void resmon_cleanup_check(noit_module_t *self, noit_check_t *check) {
874   resmon_check_info_t *rci;
875   rci = check->closure;
876   if(rci) {
877     if(rci->xpathexpr) free(rci->xpathexpr);
878     if(rci->resmod) free(rci->resmod);
879     if(rci->resserv) free(rci->resserv);
880     if(rci->xml_doc) xmlFreeDoc(rci->xml_doc);
881     http_cleanup_check(self, check);
882     memset(rci, 0, sizeof(*rci));
883   }
884 }
885 static int resmon_part_initiate_check(noit_module_t *self, noit_check_t *check,
886                                       int once, noit_check_t *parent) {
887   char xpathexpr[1024];
888   void *resmod, *resserv;
889   resmon_check_info_t *rci;
890
891   if(NOIT_CHECK_DISABLED(check) || NOIT_CHECK_KILLED(check)) return 0;
892
893   if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t));
894   rci = check->closure;
895   if(!rci->xpathexpr) {
896     if(!noit_hash_retrieve(check->config,
897                            "resmon_module", strlen("resmon_module"),
898                            &resmod)) {
899       resmod = "DUMMY_MODULE";
900     }
901     if(!noit_hash_retrieve(check->config,
902                            "resmon_service", strlen("resmon_service"),
903                            &resserv)) {
904       resserv = "DUMMY_SERVICE";
905     }
906     snprintf(xpathexpr, sizeof(xpathexpr),
907              "//ResmonResult[@module=\"%s\" and @service=\"%s\"]/*",
908              (const char *)resmod, (const char *)resserv);
909     rci->xpathexpr = strdup(xpathexpr);
910     rci->resmod = strdup(resmod);
911     rci->resserv = strdup(resserv);
912   }
913
914   if(parent && !strcmp(parent->module, "resmon")) {
915     /* Content is cached in the parent */
916     http_check_info_t *ci = &rci->http;
917     gettimeofday(&ci->finish_time, NULL);
918     resmon_part_log_results(self, check, parent);
919     return 0;
920   }
921   INITIATE_CHECK(http_initiate, self, check);
922   return 0;
923 }
924
925 static void http_eventer_free(eventer_t e, http_check_info_t *ci)
926 {
927   noitL(nldeb, "http_eventer_free(%p)\n", e);
928   if (!e)
929     return;
930   eventer_free(e);
931   return;
932 }
933
934
935 static int http_onload(noit_image_t *self) {
936   curl_global_init(CURL_GLOBAL_ALL);
937   atexit(curl_global_cleanup);
938  
939   nlerr = noit_log_stream_find("error/http");
940   nldeb = noit_log_stream_find("debug/http");
941   if(!nlerr) nlerr = noit_stderr;
942   if(!nldeb) nldeb = noit_debug;
943
944   eventer_name_callback("http/http_handler", http_handler);
945   eventer_name_callback("http/http_timeout", http_timeout);
946   eventer_name_callback("http/http_all_done", http_all_done);
947   eventer_name_callback("http/http_recurrent", http_recurrent);
948   return 0;
949 }
950
951 #include "http.xmlh"
952 noit_module_t http = {
953   {
954     NOIT_MODULE_MAGIC,
955     NOIT_MODULE_ABI_VERSION,
956     "http",
957     "libcurl-based HTTP and HTTPS resource checker",
958     http_xml_description,
959     http_onload
960   },
961   http_config,
962   http_init,
963   http_initiate_check,
964   http_cleanup_check
965 };
966
967 #include "resmon.xmlh"
968 noit_module_t resmon = {
969   {
970     NOIT_MODULE_MAGIC,
971     NOIT_MODULE_ABI_VERSION,
972     "resmon",
973     "libserf-based resmon resource checker",
974     resmon_xml_description,
975     http_onload
976   },
977   resmon_config,
978   http_init,
979   resmon_initiate_check,
980   resmon_cleanup_check
981 };
982
983 #include "resmon_part.xmlh"
984 noit_module_t resmon_part = {
985   {
986     NOIT_MODULE_MAGIC,
987     NOIT_MODULE_ABI_VERSION,
988     "resmon_part",
989     "resmon part resource checker",
990     resmon_part_xml_description,
991     http_onload
992   },
993   resmon_config,
994   http_init,
995   resmon_part_initiate_check,
996   resmon_cleanup_check
997 };
998
Note: See TracBrowser for help on using the browser.