root/src/noit_http.c

Revision 68a5668794386efc593e280d7111deca068caa5d, 38.4 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

hack to allow un-dup'd mmap buckets to be sent over HTTP when the planets align (no chunks, no compression)

  • 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 #include "noit_http.h"
35 #include "utils/noit_str.h"
36
37 #include <errno.h>
38 #include <ctype.h>
39 #include <assert.h>
40 #include <zlib.h>
41 #include <sys/mman.h>
42 #include <libxml/tree.h>
43
44 #define REQ_PAT "\r\n\r\n"
45 #define REQ_PATSIZE 4
46 #define HEADER_CONTENT_LENGTH "content-length"
47 #define HEADER_EXPECT "expect"
48
49 static noit_log_stream_t http_debug = NULL;
50 static noit_log_stream_t http_io = NULL;
51 static noit_log_stream_t http_access = NULL;
52
53 #define CTX_ADD_HEADER(a,b) \
54     noit_hash_replace(&ctx->res.headers, \
55                       strdup(a), strlen(a), strdup(b), free, free)
56 static const char _hexchars[16] =
57   {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
58 static void inplace_urldecode(char *c) {
59   char *o = c;
60   while(*c) {
61     if(*c == '%') {
62       int i, ord = 0;
63       for(i = 0; i < 2; i++) {
64         if(c[i] >= '0' && c[i] <= '9') ord = (ord << 4) | (c[i] - '0');
65         else if (c[i] >= 'a' && c[i] <= 'f') ord = (ord << 4) | (c[i] - 'a');
66         else if (c[i] >= 'A' && c[i] <= 'F') ord = (ord << 4) | (c[i] - 'A');
67         else break;
68       }
69       if(i==2) {
70         *((unsigned char *)o++) = ord;
71         c+=3;
72         continue;
73       }
74     }
75     *o++ = *c++;
76   }
77   *o = '\0';
78 }
79
80 struct bchain *bchain_alloc(size_t size, int line) {
81   struct bchain *n;
82   n = malloc(size + offsetof(struct bchain, _buff));
83   /*noitL(noit_error, "bchain_alloc(%p) : %d\n", n, line);*/
84   if(!n) return NULL;
85   n->prev = n->next = NULL;
86   n->start = n->size = 0;
87   n->allocd = size;
88   n->buff = n->_buff;
89   return n;
90 }
91 struct bchain *bchain_mmap(int fd, size_t len, int flags, off_t offset) {
92   struct bchain *n;
93   void *buff;
94   buff = mmap(NULL, len, PROT_READ, flags, fd, offset);
95   if(buff == MAP_FAILED) return NULL;
96   n = bchain_alloc(0, 0);
97   n->type = BCHAIN_MMAP;
98   n->buff = buff;
99   n->size = len;
100   n->allocd = len;
101   return n;
102 }
103 void bchain_free(struct bchain *b, int line) {
104   /*noitL(noit_error, "bchain_free(%p) : %d\n", b, line);*/
105   if(b->type == BCHAIN_MMAP) {
106     munmap(b->buff, b->allocd);
107   }
108   free(b);
109 }
110 #define ALLOC_BCHAIN(s) bchain_alloc(s, __LINE__)
111 #define FREE_BCHAIN(a) bchain_free(a, __LINE__)
112 #define RELEASE_BCHAIN(a) do { \
113   while(a) { \
114     struct bchain *__b; \
115     __b = a; \
116     a = __b->next; \
117     bchain_free(__b, __LINE__); \
118   } \
119 } while(0)
120 struct bchain *bchain_from_data(const void *d, size_t size) {
121   struct bchain *n;
122   n = ALLOC_BCHAIN(size);
123   if(!n) return NULL;
124   memcpy(n->buff, d, size);
125   n->size = size;
126   return n;
127 }
128
129 static noit_http_method
130 _method_enum(const char *s) {
131   switch(*s) {
132    case 'G':
133     if(!strcasecmp(s, "GET")) return NOIT_HTTP_GET;
134     break;
135    case 'H':
136     if(!strcasecmp(s, "HEAD")) return NOIT_HTTP_HEAD;
137     break;
138    case 'P':
139     if(!strcasecmp(s, "POST")) return NOIT_HTTP_POST;
140     break;
141    default:
142     break;
143   }
144   return NOIT_HTTP_OTHER;
145 }
146 static noit_http_protocol
147 _protocol_enum(const char *s) {
148   if(!strcasecmp(s, "HTTP/1.1")) return NOIT_HTTP11;
149   if(!strcasecmp(s, "HTTP/1.0")) return NOIT_HTTP10;
150   return NOIT_HTTP09;
151 }
152 static noit_boolean
153 _fixup_bchain(struct bchain *b) {
154   /* make sure lines (CRLF terminated) don't cross chain boundaries */
155   while(b) {
156     struct bchain *f;
157     int start_in_b, end_in_f;
158     size_t new_size;
159     const char *str_in_f;
160
161     start_in_b = b->start;
162     if(b->size > 2) {
163       if(memcmp(b->buff + b->start + b->size - 2, "\r\n", 2) == 0) {
164         b = b->next;
165         continue;
166       }
167       start_in_b = b->start + b->size - 3; /* we already checked -2 */
168       while(start_in_b >= b->start) {
169         if(b->buff[start_in_b] == '\r' && b->buff[start_in_b+1] == '\n') {
170           start_in_b += 2;
171           break;
172         }
173         start_in_b--;
174       }
175     }
176
177     /* start_in_b points to the beginning of the string we need to build
178      * into a new buffer.
179      */
180     f = b->next;
181     if(!f) return noit_false; /* Nothing left, can't complete the line */
182     str_in_f = strnstrn("\r\n", 2, f->buff + f->start, f->size);
183     if(!str_in_f) return noit_false; /* nothing in next chain -- too long */
184     str_in_f += 2;
185     end_in_f = (str_in_f - f->buff - f->start);
186     new_size = end_in_f + (b->start + b->size - start_in_b);
187     if(new_size > DEFAULT_BCHAINSIZE) return noit_false; /* string too long */
188     f = ALLOC_BCHAIN(new_size);
189     f->prev = b;
190     f->next = b->next;
191     f->start = 0;
192     f->size = new_size;
193     memcpy(f->buff, b->buff + start_in_b, b->start + b->size - start_in_b);
194     memcpy(f->buff + b->start + b->size - start_in_b,
195            f->buff + f->start, end_in_f);
196     f->next->prev = f;
197     f->prev->next = f;
198     f->prev->size -= start_in_b - b->start;
199     f->next->size -= end_in_f;
200     f->next->start += end_in_f;
201     b = f->next; /* skip f, we know it is right */
202   }
203   return noit_true;
204 }
205 static noit_boolean
206 _extract_header(char *l, const char **n, const char **v) {
207   *n = NULL;
208   if(*l == ' ' || *l == '\t') {
209     while(*l == ' ' || *l == '\t') l++;
210     *v = l;
211     return noit_true;
212   }
213   *n = l;
214   while(*l != ':' && *l) { *l = tolower(*l); l++; }
215   if(!*l) return noit_false;
216   *v = l+1;
217   /* Right trim the name */
218   *l-- = '\0';
219   while(*l == ' ' || *l == '\t') *l-- = '\0';
220   while(**v == ' ' || **v == '\t') (*v)++;
221   return noit_true;
222 }
223 static void
224 noit_http_log_request(noit_http_session_ctx *ctx) {
225   char ip[64], timestr[64];
226   double time_ms;
227   struct tm *tm, tbuf;
228   time_t now;
229   struct timeval end_time, diff;
230
231   if(ctx->req.start_time.tv_sec == 0) return;
232   gettimeofday(&end_time, NULL);
233   now = end_time.tv_sec;
234   tm = gmtime_r(&now, &tbuf);
235   strftime(timestr, sizeof(timestr), "%d/%b/%Y:%H:%M:%S -0000", tm);
236   sub_timeval(end_time, ctx->req.start_time, &diff);
237   time_ms = diff.tv_sec * 1000 + diff.tv_usec / 1000;
238   noit_convert_sockaddr_to_buff(ip, sizeof(ip), &ctx->ac->remote.remote_addr);
239   noitL(http_access, "%s - - [%s] \"%s %s%s%s %s\" %d %llu %.3f\n",
240         ip, timestr,
241         ctx->req.method_str, ctx->req.uri_str,
242         ctx->req.orig_qs ? "?" : "", ctx->req.orig_qs ? ctx->req.orig_qs : "",
243         ctx->req.protocol_str,
244         ctx->res.status_code,
245         (long long unsigned)ctx->res.bytes_written,
246         time_ms);
247 }
248
249 static int
250 _http_perform_write(noit_http_session_ctx *ctx, int *mask) {
251   int len, tlen = 0;
252   size_t attempt_write_len;
253   struct bchain **head, *b;
254  choose_bucket:
255   head = ctx->res.leader ? &ctx->res.leader : &ctx->res.output_raw;
256   b = *head;
257
258   if(!ctx->conn.e) return 0;
259 #if 0
260   if(ctx->res.output_started == noit_false) return EVENTER_EXCEPTION;
261 #endif
262   if(!b) {
263     if(ctx->res.closed) ctx->res.complete = noit_true;
264     *mask = EVENTER_EXCEPTION;
265     return tlen;
266   }
267
268   if(ctx->res.output_raw_offset >= b->size) {
269     *head = b->next;
270     FREE_BCHAIN(b);
271     b = *head;
272     if(b) b->prev = NULL;
273     ctx->res.output_raw_offset = 0;
274     goto choose_bucket;
275   }
276
277   attempt_write_len = b->size - ctx->res.output_raw_offset;
278   attempt_write_len = MIN(attempt_write_len, ctx->max_write);
279
280   len = ctx->conn.e->opset->
281           write(ctx->conn.e->fd,
282                 b->buff + b->start + ctx->res.output_raw_offset,
283                 attempt_write_len, mask, ctx->conn.e);
284   if(len == -1 && errno == EAGAIN) {
285     *mask |= EVENTER_EXCEPTION;
286     return tlen;
287   }
288   if(len == -1) {
289     /* socket error */
290     ctx->res.complete = noit_true;
291     ctx->conn.needs_close = noit_true;
292     noit_http_log_request(ctx);
293     *mask |= EVENTER_EXCEPTION;
294     return -1;
295   }
296   noitL(http_io, " http_write(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd,
297         len, len, b->buff + b->start + ctx->res.output_raw_offset);
298   ctx->res.output_raw_offset += len;
299   ctx->res.bytes_written += len;
300   tlen += len;
301   goto choose_bucket;
302 }
303 static noit_boolean
304 noit_http_request_finalize_headers(noit_http_request *req, noit_boolean *err) {
305   int start;
306   void *vval;
307   const char *mstr, *last_name = NULL;
308   struct bchain *b;
309
310   if(req->state != NOIT_HTTP_REQ_HEADERS) return noit_false;
311   if(!req->current_input) req->current_input = req->first_input;
312   if(!req->current_input) return noit_false;
313   if(req->start_time.tv_sec == 0) gettimeofday(&req->start_time, NULL);
314  restart:
315   while(req->current_input->prev &&
316         (req->current_offset < (req->current_input->start + REQ_PATSIZE - 1))) {
317     int inset;
318     /* cross bucket */
319     if(req->current_input == req->last_input &&
320        req->current_offset >= (req->last_input->start + req->last_input->size))
321       return noit_false;
322     req->current_offset++;
323     inset = req->current_offset - req->current_input->start;
324     if(memcmp(req->current_input->buff + req->current_input->start,
325               REQ_PAT + (REQ_PATSIZE - inset), inset) == 0 &&
326        memcmp(req->current_input->prev->buff +
327                 req->current_input->prev->start +
328                 req->current_input->prev->size - REQ_PATSIZE + inset,
329               REQ_PAT + inset,
330               REQ_PATSIZE - inset) == 0) goto match;
331   }
332   start = MAX(req->current_offset - REQ_PATSIZE, req->current_input->start);
333   mstr = strnstrn(REQ_PAT, REQ_PATSIZE,
334                   req->current_input->buff + start,
335                   req->current_input->size -
336                     (start - req->current_input->start));
337   if(!mstr && req->current_input->next) {
338     req->current_input = req->current_input->next;
339     req->current_offset = req->current_input->start;
340     goto restart;
341   }
342   if(!mstr) return noit_false;
343   req->current_offset = mstr - req->current_input->buff + REQ_PATSIZE;
344  match:
345   req->current_request_chain = req->first_input;
346   noitL(http_debug, " noit_http_request_finalize : match(%d in %d)\n",
347         (int)(req->current_offset - req->current_input->start),
348         (int)req->current_input->size);
349   if(req->current_offset <
350      req->current_input->start + req->current_input->size) {
351     /* There are left-overs */
352     int lsize = req->current_input->size - req->current_offset;
353     noitL(http_debug, " noit_http_request_finalize -- leftovers: %d\n", lsize);
354     req->first_input = ALLOC_BCHAIN(lsize);
355     req->first_input->prev = NULL;
356     req->first_input->next = req->current_input->next;
357     req->first_input->start = 0;
358     req->first_input->size = lsize;
359     memcpy(req->first_input->buff,
360            req->current_input->buff + req->current_offset,
361            req->first_input->size);
362     req->current_input->size -= lsize;
363     if(req->last_input == req->current_input)
364       req->last_input = req->first_input;
365     else
366       FREE_BCHAIN(req->current_input);
367   }
368   else {
369     req->first_input = req->last_input = NULL;
370   }
371   req->current_input = NULL;
372   req->current_offset = 0;
373
374   /* Now we need to dissect the current_request_chain into an HTTP request */
375   /* First step: make sure that no line crosses a chain boundary by
376    * inserting new chains as necessary.
377    */
378   if(!_fixup_bchain(req->current_request_chain)) {
379     *err = noit_true;
380     return noit_false;
381   }
382   /* Second step is to parse out the request itself */
383   for(b = req->current_request_chain; b; b = b->next) {
384     char *curr_str, *next_str;
385     b->buff[b->start + b->size - 2] = '\0';
386     curr_str = b->buff + b->start;
387     do {
388       next_str = strstr(curr_str, "\r\n");
389       if(next_str) {
390         *((char *)next_str) = '\0';
391         next_str += 2;
392       }
393       if(req->method_str && *curr_str == '\0')
394         break; /* our CRLFCRLF... end of req */
395 #define FAIL do { *err = noit_true; return noit_false; } while(0)
396       if(!req->method_str) { /* request line */
397         req->method_str = (char *)curr_str;
398         req->uri_str = strchr(curr_str, ' ');
399         if(!req->uri_str) FAIL;
400         *(req->uri_str) = '\0';
401         req->uri_str++;
402         req->protocol_str = strchr(req->uri_str, ' ');
403         if(!req->protocol_str) FAIL;
404         *(req->protocol_str) = '\0';
405         req->protocol_str++;
406         req->method = _method_enum(req->method_str);
407         req->protocol = _protocol_enum(req->protocol_str);
408         req->opts |= NOIT_HTTP_CLOSE;
409         if(req->protocol == NOIT_HTTP11) req->opts |= NOIT_HTTP_CHUNKED;
410       }
411       else { /* request headers */
412         const char *name, *value;
413         if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
414         if(!name && !last_name) FAIL;
415         if(!strcmp(name ? name : last_name, "accept-encoding")) {
416           if(strstr(value, "gzip")) req->opts |= NOIT_HTTP_GZIP;
417           if(strstr(value, "deflate")) req->opts |= NOIT_HTTP_DEFLATE;
418         }
419         if(name)
420           noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
421                             NULL, NULL);
422         else {
423           struct bchain *b;
424           const char *prefix = NULL;
425           int l1, l2;
426           noit_hash_retr_str(&req->headers, last_name, strlen(last_name),
427                              &prefix);
428           if(!prefix) FAIL;
429           l1 = strlen(prefix);
430           l2 = strlen(value);
431           b = ALLOC_BCHAIN(l1 + l2 + 2);
432           b->next = req->current_request_chain;
433           b->next->prev = b;
434           req->current_request_chain = b;
435           b->size = l1 + l2 + 2;
436           memcpy(b->buff, prefix, l1);
437           b->buff[l1] = ' ';
438           memcpy(b->buff + l1 + 1, value, l2);
439           b->buff[l1 + 1 + l2] = '\0';
440           noit_hash_replace(&req->headers, last_name, strlen(last_name),
441                             b->buff, NULL, NULL);
442         }
443         if(name) last_name = name;
444       }
445       curr_str = next_str;
446     } while(next_str);
447   }
448
449   /* headers are done... we could need to read a payload */
450   if(noit_hash_retrieve(&req->headers,
451                         HEADER_CONTENT_LENGTH,
452                         sizeof(HEADER_CONTENT_LENGTH)-1, &vval)) {
453     const char *val = vval;
454     req->has_payload = noit_true;
455     req->content_length = strtoll(val, NULL, 10);
456   }
457   if(noit_hash_retrieve(&req->headers, HEADER_EXPECT,
458                         sizeof(HEADER_EXPECT)-1, &vval)) {
459     const char *val = vval;
460     if(strncmp(val, "100-", 4) || /* Bad expect header */
461        req->has_payload == noit_false) /* expect, but no content length */
462       FAIL;
463     /* We need to tell the client to "go-ahead" -- HTTP sucks */
464     req->state = NOIT_HTTP_REQ_EXPECT;
465     return noit_false;
466   }
467   if(req->content_length > 0) {
468     /* switch modes... let's go read the payload */
469     req->state = NOIT_HTTP_REQ_PAYLOAD;
470     return noit_false;
471   }
472
473   req->complete = noit_true;
474   return noit_true;
475 }
476 void
477 noit_http_process_querystring(noit_http_request *req) {
478   char *cp, *interest, *brk;
479   cp = strchr(req->uri_str, '?');
480   if(!cp) return;
481   *cp++ = '\0';
482   req->orig_qs = strdup(cp);
483   for (interest = strtok_r(cp, "&", &brk);
484        interest;
485        interest = strtok_r(NULL, "&", &brk)) {
486     char *eq;
487     eq = strchr(interest, '=');
488     if(!eq) {
489       inplace_urldecode(interest);
490       noit_hash_store(&req->querystring, interest, strlen(interest), NULL);
491     }
492     else {
493       *eq++ = '\0';
494       inplace_urldecode(interest);
495       inplace_urldecode(eq);
496       noit_hash_store(&req->querystring, interest, strlen(interest), eq);
497     }
498   }
499 }
500 static noit_boolean
501 noit_http_request_finalize_payload(noit_http_request *req, noit_boolean *err) {
502   req->complete = noit_true;
503   return noit_true;
504 }
505 static noit_boolean
506 noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
507   if(req->state == NOIT_HTTP_REQ_HEADERS)
508     if(noit_http_request_finalize_headers(req, err)) return noit_true;
509   if(req->state == NOIT_HTTP_REQ_EXPECT) return noit_false;
510   if(req->state == NOIT_HTTP_REQ_PAYLOAD)
511     if(noit_http_request_finalize_payload(req, err)) return noit_true;
512   return noit_false;
513 }
514 static int
515 noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
516   struct bchain *in;
517   noit_boolean rv, err = noit_false;
518
519   if(mask & EVENTER_EXCEPTION) {
520    full_error:
521     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
522     ctx->conn.e = NULL;
523     return 0;
524   }
525   if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
526
527   /* We could have a complete request in the tail of a previous request */
528   rv = noit_http_request_finalize(&ctx->req, &err);
529   if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
530   if(err == noit_true) goto full_error;
531
532   while(1) {
533     int len;
534
535     in = ctx->req.last_input;
536     if(!in) {
537       in = ctx->req.first_input = ctx->req.last_input =
538         ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
539       if(!in) goto full_error;
540     }
541     if(in->size > 0 && /* we've read something */
542        DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
543        DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
544       in->next = ctx->req.last_input =
545         ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
546       in->next->prev = in;
547       in = in->next;
548       if(!in) goto full_error;
549     }
550
551     len = ctx->conn.e->opset->read(ctx->conn.e->fd,
552                                    in->buff + in->start + in->size,
553                                    in->allocd - in->size - in->start,
554                                    &mask, ctx->conn.e);
555     noitL(http_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, len);
556     noitL(http_io, " noit_http:read(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd, len, len, in->buff + in->start + in->size);
557     if(len == -1 && errno == EAGAIN) return mask;
558     if(len <= 0) goto full_error;
559     if(len > 0) in->size += len;
560     rv = noit_http_request_finalize(&ctx->req, &err);
561     if(len == -1 || err == noit_true) goto full_error;
562     if(ctx->req.state == NOIT_HTTP_REQ_EXPECT) {
563       const char *expect;
564       ctx->req.state = NOIT_HTTP_REQ_PAYLOAD;
565       assert(ctx->res.leader == NULL);
566       expect = "HTTP/1.1 100 Continue\r\n\r\n";
567       ctx->res.leader = bchain_from_data(expect, strlen(expect));
568       _http_perform_write(ctx, &mask);
569       ctx->req.complete = noit_true;
570       if(ctx->res.leader != NULL) return mask;
571     }
572     if(rv == noit_true) return mask | EVENTER_WRITE | EVENTER_EXCEPTION;
573   }
574   /* Not reached:
575    * return EVENTER_READ | EVENTER_EXCEPTION;
576    */
577 }
578 noit_boolean
579 noit_http_session_prime_input(noit_http_session_ctx *ctx,
580                               const void *data, size_t len) {
581   if(ctx->req.first_input != NULL) return noit_false;
582   if(len > DEFAULT_BCHAINSIZE) return noit_false;
583   ctx->req.first_input = ctx->req.last_input =
584       ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
585   memcpy(ctx->req.first_input->buff, data, len);
586   ctx->req.first_input->size = len;
587   return noit_true;
588 }
589
590 void
591 noit_http_request_release(noit_http_session_ctx *ctx) {
592   noit_hash_destroy(&ctx->req.querystring, NULL, NULL);
593   noit_hash_destroy(&ctx->req.headers, NULL, NULL);
594   /* If we expected a payload, we expect a trailing \r\n */
595   if(ctx->req.has_payload) {
596     int drained, mask;
597     ctx->drainage = ctx->req.content_length - ctx->req.content_length_read;
598     /* best effort, we'll drain it before the next request anyway */
599     drained = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
600     ctx->drainage -= drained;
601   }
602   RELEASE_BCHAIN(ctx->req.current_request_chain);
603   if(ctx->req.orig_qs) free(ctx->req.orig_qs);
604   memset(&ctx->req.state, 0,
605          sizeof(ctx->req) - (unsigned long)&(((noit_http_request *)0)->state));
606 }
607 void
608 noit_http_response_release(noit_http_session_ctx *ctx) {
609   noit_hash_destroy(&ctx->res.headers, free, free);
610   if(ctx->res.status_reason) free(ctx->res.status_reason);
611   RELEASE_BCHAIN(ctx->res.leader);
612   RELEASE_BCHAIN(ctx->res.output);
613   RELEASE_BCHAIN(ctx->res.output_raw);
614   memset(&ctx->res, 0, sizeof(ctx->res));
615 }
616 void
617 noit_http_ctx_session_release(noit_http_session_ctx *ctx) {
618   if(noit_atomic_dec32(&ctx->ref_cnt) == 0) {
619     noit_http_request_release(ctx);
620     if(ctx->req.first_input) RELEASE_BCHAIN(ctx->req.first_input);
621     noit_http_response_release(ctx);
622     free(ctx);
623   }
624 }
625 void
626 noit_http_ctx_acceptor_free(void *v) {
627   noit_http_ctx_session_release((noit_http_session_ctx *)v);
628 }
629 int
630 noit_http_session_req_consume(noit_http_session_ctx *ctx,
631                               void *buf, size_t len, int *mask) {
632   size_t bytes_read = 0;
633   /* We attempt to consume from the first_input */
634   struct bchain *in, *tofree;
635   noitL(http_debug, " ... noit_http_session_req_consume(%d) %d of %d\n",
636         ctx->conn.e->fd, (int)len,
637         (int)(ctx->req.content_length - ctx->req.content_length_read));
638   len = MIN(len, ctx->req.content_length - ctx->req.content_length_read);
639   while(bytes_read < len) {
640     int crlen = 0;
641     in = ctx->req.first_input;
642     while(in && bytes_read < len) {
643       int partial_len = MIN(in->size, len - bytes_read);
644       if(buf) memcpy((char *)buf+bytes_read, in->buff+in->start, partial_len);
645       bytes_read += partial_len;
646       ctx->req.content_length_read += partial_len;
647       noitL(http_debug, " ... filling %d bytes (read through %d/%d)\n",
648             (int)bytes_read, (int)ctx->req.content_length_read,
649             (int)ctx->req.content_length);
650       in->start += partial_len;
651       in->size -= partial_len;
652       if(in->size == 0) {
653         tofree = in;
654         ctx->req.first_input = in = in->next;
655         tofree->next = NULL;
656         RELEASE_BCHAIN(tofree);
657         if(in == NULL) {
658           ctx->req.last_input = NULL;
659           noitL(http_debug, " ... noit_http_session_req_consume = %d\n",
660                 (int)bytes_read);
661           return bytes_read;
662         }
663       }
664     }
665     while(bytes_read + crlen < len) {
666       int rlen;
667       in = ctx->req.last_input;
668       if(!in)
669         in = ctx->req.first_input = ctx->req.last_input =
670             ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
671       else if(in->start + in->size >= in->allocd) {
672         in->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
673         in = ctx->req.last_input = in->next;
674       }
675       /* pull next chunk */
676       rlen = ctx->conn.e->opset->read(ctx->conn.e->fd,
677                                       in->buff + in->start + in->size,
678                                       in->allocd - in->size - in->start,
679                                       mask, ctx->conn.e);
680       noitL(http_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, rlen);
681     noitL(http_io, " noit_http:read(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd, rlen, rlen, in->buff + in->start + in->size);
682       if(rlen == -1 && errno == EAGAIN) {
683         /* We'd block to read more, but we have data,
684          * so do a short read */
685         if(ctx->req.first_input && ctx->req.first_input->size) break;
686         /* We've got nothing... */
687         noitL(http_debug, " ... noit_http_session_req_consume = -1 (EAGAIN)\n");
688         return -1;
689       }
690       if(rlen <= 0) {
691         noitL(http_debug, " ... noit_http_session_req_consume = -1 (error)\n");
692         return -1;
693       }
694       in->size += rlen;
695       crlen += rlen;
696     }
697   }
698   /* NOT REACHED */
699   return bytes_read;
700 }
701
702 int
703 noit_http_session_drive(eventer_t e, int origmask, void *closure,
704                         struct timeval *now, int *done) {
705   noit_http_session_ctx *ctx = closure;
706   int rv = 0;
707   int mask = origmask;
708
709   if(origmask & EVENTER_EXCEPTION)
710     goto abort_drive;
711
712   /* Drainage -- this is as nasty as it sounds
713    * The last request could have unread upload content, we would have
714    * noted that in noit_http_request_release.
715    */
716   noitL(http_debug, " -> noit_http_session_drive(%d) [%x]\n", e->fd, origmask);
717   while(ctx->drainage > 0) {
718     int len;
719     noitL(http_debug, "   ... draining last request(%d)\n", e->fd);
720     len = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
721     if(len == -1 && errno == EAGAIN) {
722       noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask);
723       return mask;
724     }
725     if(len <= 0) goto abort_drive;
726     ctx->drainage -= len;
727   }
728
729  next_req:
730   if(ctx->req.complete != noit_true) {
731     int maybe_write_mask;
732     noitL(http_debug, "   -> noit_http_complete_request(%d)\n", e->fd);
733     mask = noit_http_complete_request(ctx, origmask);
734     noitL(http_debug, "   <- noit_http_complete_request(%d) = %d\n",
735           e->fd, mask);
736     _http_perform_write(ctx, &maybe_write_mask);
737     if(ctx->conn.e == NULL) goto release;
738     if(ctx->req.complete != noit_true) {
739       noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd,
740             mask|maybe_write_mask);
741       return mask | maybe_write_mask;
742     }
743     noitL(http_debug, "HTTP start request (%s)\n", ctx->req.uri_str);
744     noit_http_process_querystring(&ctx->req);
745   }
746
747   /* only dispatch if the response is not closed */
748   if(ctx->res.closed == noit_false) {
749     noitL(http_debug, "   -> dispatch(%d)\n", e->fd);
750     rv = ctx->dispatcher(ctx);
751     noitL(http_debug, "   <- dispatch(%d) = %d\n", e->fd, rv);
752   }
753
754   _http_perform_write(ctx, &mask);
755   if(ctx->res.complete == noit_true &&
756      ctx->conn.e &&
757      ctx->conn.needs_close == noit_true) {
758    abort_drive:
759     noit_http_log_request(ctx);
760     if(ctx->conn.e) {
761       ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
762       ctx->conn.e = NULL;
763     }
764     goto release;
765   }
766   if(ctx->res.complete == noit_true) {
767     noit_http_log_request(ctx);
768     noit_http_request_release(ctx);
769     noit_http_response_release(ctx);
770   }
771   if(ctx->req.complete == noit_false) goto next_req;
772   if(ctx->conn.e) {
773     noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask|rv);
774     return mask | rv;
775   }
776   noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
777   goto abort_drive;
778
779  release:
780   *done = 1;
781   /* We're about to release, unhook us from the acceptor_closure so we
782    * don't get double freed */
783   if(ctx->ac->service_ctx == ctx) ctx->ac->service_ctx = NULL;
784   noit_http_ctx_session_release(ctx);
785   noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
786   return 0;
787 }
788
789 noit_http_session_ctx *
790 noit_http_session_ctx_new(noit_http_dispatch_func f, void *c, eventer_t e,
791                           acceptor_closure_t *ac) {
792   noit_http_session_ctx *ctx;
793   ctx = calloc(1, sizeof(*ctx));
794   ctx->ref_cnt = 1;
795   ctx->req.complete = noit_false;
796   ctx->conn.e = e;
797   ctx->max_write = DEFAULT_MAXWRITE;
798   ctx->dispatcher = f;
799   ctx->dispatcher_closure = c;
800   ctx->ac = ac;
801   return ctx;
802 }
803
804 noit_boolean
805 noit_http_response_status_set(noit_http_session_ctx *ctx,
806                               int code, const char *reason) {
807   if(ctx->res.output_started == noit_true) return noit_false;
808   ctx->res.protocol = ctx->req.protocol;
809   if(code < 100 || code > 999) return noit_false;
810   ctx->res.status_code = code;
811   if(ctx->res.status_reason) free(ctx->res.status_reason);
812   ctx->res.status_reason = strdup(reason);
813   return noit_true;
814 }
815 noit_boolean
816 noit_http_response_header_set(noit_http_session_ctx *ctx,
817                               const char *name, const char *value) {
818   if(ctx->res.output_started == noit_true) return noit_false;
819   noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
820                     strdup(value), free, free);
821   return noit_true;
822 }
823 noit_boolean
824 noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
825   if(ctx->res.output_started == noit_true) return noit_false;
826   /* transfer and content encodings only allowed in HTTP/1.1 */
827   if(ctx->res.protocol != NOIT_HTTP11 &&
828      (opt & NOIT_HTTP_CHUNKED))
829     return noit_false;
830   if(ctx->res.protocol != NOIT_HTTP11 &&
831      (opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
832     return noit_false;
833   if(((ctx->res.output_options | opt) &
834       (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
835         (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
836     return noit_false;
837
838   /* Check out "accept" set */
839   if(!(opt & ctx->req.opts)) return noit_false;
840
841   ctx->res.output_options |= opt;
842   if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
843     CTX_ADD_HEADER("Transfer-Encoding", "chunked");
844   if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
845     CTX_ADD_HEADER("Vary", "Accept-Encoding");
846     if(ctx->res.output_options & NOIT_HTTP_GZIP)
847       CTX_ADD_HEADER("Content-Encoding", "gzip");
848     else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
849       CTX_ADD_HEADER("Content-Encoding", "deflate");
850   }
851   if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
852     CTX_ADD_HEADER("Connection", "close");
853     ctx->conn.needs_close = noit_true;
854   }
855   return noit_true;
856 }
857 noit_boolean
858 noit_http_response_append(noit_http_session_ctx *ctx,
859                           const void *b, size_t l) {
860   struct bchain *o;
861   int boff = 0;
862   if(ctx->res.closed == noit_true) return noit_false;
863   if(ctx->res.output_started == noit_true &&
864      !(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
865     return noit_false;
866   if(!ctx->res.output)
867     assert(ctx->res.output = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE));
868   o = ctx->res.output;
869   while(o->next) o = o->next;
870   while(l > 0) {
871     if(o->allocd == o->start + o->size) {
872       /* Filled up, need another */
873       o->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
874       o->next->prev = o->next;
875       o = o->next;
876     }
877     if(o->allocd > o->start + o->size) {
878       int tocopy = MIN(l, o->allocd - o->start - o->size);
879       memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
880       o->size += tocopy;
881       boff += tocopy;
882       l -= tocopy;
883     }
884   }
885   return noit_true;
886 }
887 noit_boolean
888 noit_http_response_append_bchain(noit_http_session_ctx *ctx,
889                                  struct bchain *b) {
890   struct bchain *o;
891   if(ctx->res.closed == noit_true) return noit_false;
892   if(ctx->res.output_started == noit_true &&
893      !(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
894     return noit_false;
895   if(!ctx->res.output)
896     ctx->res.output = b;
897   else {
898     o = ctx->res.output;
899     while(o->next) o = o->next;
900     o->allocd = o->size; /* so we know it is full */
901     o->next = b;
902     b->prev = o;
903   }
904   return noit_true;
905 }
906 noit_boolean
907 noit_http_response_append_mmap(noit_http_session_ctx *ctx,
908                                int fd, size_t len, int flags, off_t offset) {
909   struct bchain *n;
910   n = bchain_mmap(fd, len, flags, offset);
911   if(n == NULL) return noit_false;
912   return noit_http_response_append_bchain(ctx, n);
913 }
914 static int
915 _http_construct_leader(noit_http_session_ctx *ctx) {
916   int len = 0, tlen;
917   struct bchain *b;
918   const char *protocol_str;
919   const char *key, *value;
920   int klen;
921   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
922
923   assert(!ctx->res.leader);
924   ctx->res.leader = b = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
925
926   protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
927                    "HTTP/1.1" :
928                    (ctx->res.protocol == NOIT_HTTP10 ?
929                      "HTTP/1.0" :
930                      "HTTP/0.9");
931   tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
932                   protocol_str, ctx->res.status_code, ctx->res.status_reason);
933   if(tlen < 0) return -1;
934   len = b->size = tlen;
935
936 #define CTX_LEADER_APPEND(s, slen) do { \
937   if(b->size + slen > DEFAULT_BCHAINSIZE) { \
938     b->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE); \
939     assert(b->next); \
940     b->next->prev = b; \
941     b = b->next; \
942   } \
943   assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
944   memcpy(b->buff + b->start + b->size, s, slen); \
945   b->size += slen; \
946 } while(0)
947   while(noit_hash_next_str(&ctx->res.headers, &iter,
948                            &key, &klen, &value)) {
949     int vlen = strlen(value);
950     CTX_LEADER_APPEND(key, klen);
951     CTX_LEADER_APPEND(": ", 2);
952     CTX_LEADER_APPEND(value, vlen);
953     CTX_LEADER_APPEND("\r\n", 2);
954   }
955   CTX_LEADER_APPEND("\r\n", 2);
956   return len;
957 }
958 static int memgzip2(Bytef *dest, uLongf *destLen,
959                     const Bytef *source, uLong sourceLen, int level) {
960   z_stream stream;
961   int err;
962
963   memset(&stream, 0, sizeof(stream));
964   stream.next_in = (Bytef*)source;
965   stream.avail_in = (uInt)sourceLen;
966   stream.next_out = dest;
967   stream.avail_out = (uInt)*destLen;
968   if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
969
970   err = deflateInit2(&stream, level, Z_DEFLATED, 15+16, 8,
971                      Z_DEFAULT_STRATEGY);
972   if (err != Z_OK) return err;
973
974   err = deflate(&stream, Z_FINISH);
975   if (err != Z_STREAM_END) {
976     deflateEnd(&stream);
977     return err == Z_OK ? Z_BUF_ERROR : err;
978   }
979   *destLen = stream.total_out;
980
981   err = deflateEnd(&stream);
982   return err;
983 }
984 static noit_boolean
985 _http_encode_chain(struct bchain *out, struct bchain *in, int opts) {
986   /* implement gzip and deflate! */
987   if(opts & NOIT_HTTP_GZIP) {
988     uLongf olen;
989     olen = out->allocd - out->start;
990     if(Z_OK != memgzip2((Bytef *)(out->buff + out->start), &olen,
991                         (Bytef *)(in->buff + in->start), (uLong)in->size,
992                         9)) {
993       noitL(noit_error, "zlib compress2 error\n");
994       return noit_false;
995     }
996     out->size += olen;
997   }
998   else if(opts & NOIT_HTTP_DEFLATE) {
999     uLongf olen;
1000     olen = out->allocd - out->start;
1001     if(Z_OK != compress2((Bytef *)(out->buff + out->start), &olen,
1002                          (Bytef *)(in->buff + in->start), (uLong)in->size,
1003                          9)) {
1004       noitL(noit_error, "zlib compress2 error\n");
1005       return noit_false;
1006     }
1007     out->size += olen;
1008   }
1009   else {
1010     if(in->size > out->allocd - out->start) return noit_false;
1011     memcpy(out->buff + out->start, in->buff + in->start, in->size);
1012     out->size += in->size;
1013   }
1014   return noit_true;
1015 }
1016 struct bchain *
1017 noit_http_process_output_bchain(noit_http_session_ctx *ctx,
1018                                 struct bchain *in) {
1019   struct bchain *out;
1020   int ilen, maxlen = in->size, hexlen;
1021   int opts = ctx->res.output_options;
1022
1023   if(in->type == BCHAIN_MMAP &&
1024      0 == (opts & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE | NOIT_HTTP_CHUNKED))) {
1025     out = ALLOC_BCHAIN(0);
1026     out->buff = in->buff;
1027     out->type = in->type;
1028     out->size = in->size;
1029     out->allocd = in->allocd;
1030     in->type = BCHAIN_INLINE;
1031     return out;
1032   }
1033   /* a chunked header looks like: hex*\r\ndata\r\n */
1034   /* let's assume that content never gets "larger" */
1035   if(opts & NOIT_HTTP_GZIP) maxlen = deflateBound(NULL, in->size);
1036   else if(opts & NOIT_HTTP_DEFLATE) maxlen = compressBound(in->size);
1037
1038   /* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
1039   ilen = maxlen;
1040   hexlen = 0;
1041   while(ilen) { ilen >>= 4; hexlen++; }
1042   if(hexlen == 0) hexlen = 1;
1043
1044   out = ALLOC_BCHAIN(hexlen + 4 + maxlen);
1045   /* if we're chunked, let's give outselved hexlen + 2 prefix space */
1046   if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
1047   if(_http_encode_chain(out, in, opts) == noit_false) {
1048     free(out);
1049     return NULL;
1050   }
1051   /* Too long! Out "larger" assumption is bad */
1052   if(opts & NOIT_HTTP_CHUNKED) {
1053     ilen = out->size;
1054     assert(out->start+out->size+2 <= out->allocd);
1055     out->buff[out->start + out->size++] = '\r';
1056     out->buff[out->start + out->size++] = '\n';
1057     out->start = 0;
1058     /* terminate */
1059     out->size += 2;
1060     out->buff[hexlen] = '\r';
1061     out->buff[hexlen+1] = '\n';
1062     /* backfill */
1063     out->size += hexlen;
1064     while(hexlen > 0) {
1065       out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
1066       ilen >>= 4;
1067       hexlen--;
1068     }
1069     while(out->buff[out->start] == '0') {
1070       out->start++;
1071       out->size--;
1072     }
1073   }
1074   return out;
1075 }
1076 noit_boolean
1077 noit_http_response_flush(noit_http_session_ctx *ctx, noit_boolean final) {
1078   struct bchain *o, *r;
1079   int mask, rv;
1080
1081   if(ctx->res.closed == noit_true) return noit_false;
1082   if(ctx->res.output_started == noit_false) {
1083     _http_construct_leader(ctx);
1084     ctx->res.output_started = noit_true;
1085   }
1086   /* encode output to output_raw */
1087   r = ctx->res.output_raw;
1088   while(r && r->next) r = r->next;
1089   /* r is the last raw output link */
1090   o = ctx->res.output;
1091   /* o is the first output link to process */
1092   while(o) {
1093     struct bchain *tofree, *n;
1094     n = noit_http_process_output_bchain(ctx, o);
1095     if(!n) {
1096       /* Bad, response stops here! */
1097       noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
1098       while(o) { tofree = o; o = o->next; free(tofree); }
1099       final = noit_true;
1100       break;
1101     }
1102     if(r) {
1103       r->next = n;
1104       n->prev = r;
1105       r = n;
1106     }
1107     else {
1108       r = ctx->res.output_raw = n;
1109     }
1110     tofree = o; o = o->next; FREE_BCHAIN(tofree); /* advance and free */
1111   }
1112   ctx->res.output = NULL;
1113   if(final) {
1114     struct bchain *n;
1115     ctx->res.closed = noit_true;
1116     if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
1117       n = bchain_from_data("0\r\n\r\n", 5);
1118     else
1119       n = bchain_from_data("\r\n", 2);
1120     if(r) {
1121       r->next = n;
1122       n->prev = r;
1123     }
1124     else {
1125       ctx->res.output_raw = n;
1126     }
1127   }
1128
1129   rv = _http_perform_write(ctx, &mask);
1130   if(ctx->conn.e) {
1131     eventer_update(ctx->conn.e, mask);
1132   }
1133   if(rv < 0) return noit_false;
1134   /* If the write fails completely, the event will not be closed,
1135    * the following should not trigger the false case.
1136    */
1137   return ctx->conn.e ? noit_true : noit_false;
1138 }
1139
1140 noit_boolean
1141 noit_http_response_end(noit_http_session_ctx *ctx) {
1142   if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
1143   return noit_true;
1144 }
1145
1146
1147 /* Helper functions */
1148
1149 static int
1150 noit_http_write_xml(void *vctx, const char *buffer, int len) {
1151   if(noit_http_response_append((noit_http_session_ctx *)vctx, buffer, len))
1152     return len;
1153   return -1;
1154 }
1155 static int
1156 noit_http_close_xml(void *vctx) {
1157   noit_http_response_end((noit_http_session_ctx *)vctx);
1158   return 0;
1159 }
1160 void
1161 noit_http_response_xml(noit_http_session_ctx *ctx, xmlDocPtr doc) {
1162   xmlOutputBufferPtr out;
1163   xmlCharEncodingHandlerPtr enc;
1164   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1165   out = xmlOutputBufferCreateIO(noit_http_write_xml,
1166                                 noit_http_close_xml,
1167                                 ctx, enc);
1168   xmlSaveFormatFileTo(out, doc, "utf8", 1);
1169 }
1170
1171 void
1172 noit_http_init() {
1173   http_debug = noit_log_stream_find("debug/http");
1174   http_access = noit_log_stream_find("http/access");
1175   http_io = noit_log_stream_find("http/io");
1176 }
Note: See TracBrowser for help on using the browser.