root/src/noit_http.c

Revision 55168c75e76b2c0c385088d498edc4e1a4842332, 20.2 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

scaffolding for HTTP server..., refs #64

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7 #include "noit_http.h"
8 #include "utils/noit_str.h"
9
10 #include <errno.h>
11 #include <ctype.h>
12 #include <assert.h>
13
14 #define REQ_PAT "\r\n\r\n"
15 #define REQ_PATSIZE 4
16
17 #define CTX_ADD_HEADER(a,b) \
18     noit_hash_replace(&ctx->res.headers, \
19                       strdup(a), strlen(a), strdup(b), free, free)
20 static const char _hexchars[16] =
21   {'0','1','2','3','4','5','6','7','8','9','a','b','c','e','f'};
22
23 struct bchain *bchain_alloc(size_t size) {
24   struct bchain *n;
25   n = malloc(size + ((void *)n->buff - (void *)n));
26   if(!n) return NULL;
27   n->prev = n->next = NULL;
28   n->start = n->size = 0;
29   n->allocd = size;
30   return n;
31 }
32 struct bchain *bchain_from_data(void *d, size_t size) {
33   struct bchain *n;
34   n = bchain_alloc(size);
35   if(!n) return NULL;
36   memcpy(n->buff, d, size);
37   n->size = size;
38   return n;
39 }
40 void bchain_free(struct bchain *b) {
41   free(b);
42 }
43
44 static noit_http_method
45 _method_enum(const char *s) {
46   switch(*s) {
47    case 'G':
48     if(!strcasecmp(s, "GET")) return NOIT_HTTP_GET;
49     break;
50    case 'H':
51     if(!strcasecmp(s, "HEAD")) return NOIT_HTTP_HEAD;
52     break;
53    default:
54     break;
55   }
56   return NOIT_HTTP_OTHER;
57 }
58 static noit_http_protocol
59 _protocol_enum(const char *s) {
60   if(!strcasecmp(s, "HTTP/1.1")) return NOIT_HTTP11;
61   if(!strcasecmp(s, "HTTP/1.0")) return NOIT_HTTP10;
62   return NOIT_HTTP09;
63 }
64 static noit_boolean
65 _fixup_bchain(struct bchain *b) {
66   /* make sure lines (CRLF terminated) don't cross chain boundaries */
67   while(b) {
68     struct bchain *f;
69     int start_in_b, end_in_f;
70     size_t new_size;
71     const char *str_in_f;
72
73     start_in_b = b->start;
74     if(b->size > 2) {
75       if(memcmp(b->buff + b->start + b->size - 2, "\r\n", 2) == 0) {
76         b = b->next;
77         continue;
78       }
79       start_in_b = b->start + b->size - 3; /* we already checked -2 */
80       while(start_in_b >= b->start) {
81         if(memcmp(b->buff + start_in_b, "\r\n", 2) == 0) {
82           start_in_b += 2;
83           break;
84         }
85       }
86     }
87
88     /* start_in_b points to the beginning of the string we need to build
89      * into a new buffer.
90      */
91     f = b->next;
92     if(!f) return noit_false; /* Nothing left, can't complete the line */
93     str_in_f = strnstrn("\r\n", 2, f->buff + f->start, f->size);
94     if(!str_in_f) return noit_false; /* nothing in next chain -- too long */
95     str_in_f += 2;
96     end_in_f = (str_in_f - f->buff - f->start);
97     new_size = end_in_f + (b->start + b->size - start_in_b);
98     if(new_size > DEFAULT_BCHAINSIZE) return noit_false; /* string too long */
99     f = bchain_alloc(new_size);
100     f->prev = b;
101     f->next = b->next;
102     f->start = 0;
103     f->size = new_size;
104     memcpy(f->buff, b->buff + start_in_b, b->start + b->size - start_in_b);
105     memcpy(f->buff + b->start + b->size - start_in_b,
106            f->buff + f->start, end_in_f);
107     f->next->prev = f;
108     f->prev->next = f;
109     f->prev->size -= start_in_b - b->start;
110     f->next->size -= end_in_f;
111     f->next->start += end_in_f;
112     b = f->next; /* skip f, we know it is right */
113   }
114   return noit_true;
115 }
116 static noit_boolean
117 _extract_header(char *l, const char **n, const char **v) {
118   *n = NULL;
119   if(*l == ' ' || *l == '\t') {
120     while(*l == ' ' || *l == '\t') l++;
121     *v = l;
122     return noit_true;
123   }
124   *n = l;
125   while(*l != ':' && *l) { *l = tolower(*l); l++; }
126   if(!*l) return noit_false;
127   *v = l+1;
128   /* Right trim the name */
129   *l-- = '\0';
130   while(*l == ' ' || *l == '\t') *l-- = '\0';
131   while(**v == ' ' || **v == '\t') (*v)++;
132   return noit_true;
133 }
134 static int
135 _http_perform_write(noit_http_session_ctx *ctx, int *mask) {
136   int len;
137   struct bchain **head, *b;
138  choose_bucket:
139   head = ctx->res.leader ? &ctx->res.leader : &ctx->res.output_raw;
140   b = *head;
141
142   if(!ctx->conn.e) return 0;
143   if(ctx->res.output_started == noit_false) return EVENTER_EXCEPTION;
144   if(!b) {
145     if(ctx->res.closed) ctx->res.complete = noit_true;
146     return EVENTER_EXCEPTION;
147   }
148
149   if(ctx->res.output_raw_offset >= b->size) {
150     *head = b->next;
151     free(b);
152     b = *head;
153     if(b) b->prev = NULL;
154     ctx->res.output_raw_offset = 0;
155     goto choose_bucket;
156   }
157
158   len = ctx->conn.e->opset->
159           write(ctx->conn.e->fd,
160                 b->buff + b->start + ctx->res.output_raw_offset,
161                 b->size - ctx->res.output_raw_offset,
162                 mask, ctx->conn.e);
163   if(len == -1 && errno == EAGAIN) return (*mask | EVENTER_EXCEPTION);
164   if(len == -1) {
165     /* socket error */
166     ctx->conn.e->opset->close(ctx->conn.e->fd, mask, ctx->conn.e);
167     ctx->conn.e = NULL;
168     return 0;
169   }
170   ctx->res.output_raw_offset += len;
171   goto choose_bucket;
172 }
173 static noit_boolean
174 noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
175   int start;
176   const char *mstr, *last_name;
177   struct bchain *b;
178
179   if(!req->current_input) req->current_input = req->first_input;
180   if(!req->current_input) return noit_false;
181  restart:
182   while(req->current_input->prev &&
183         (req->current_offset < (req->current_input->start + REQ_PATSIZE - 1))) {
184     int inset;
185     /* cross bucket */
186     if(req->current_input == req->last_input &&
187        req->current_offset >= (req->last_input->start + req->last_input->size))
188       return false;
189     req->current_offset++;
190     inset = req->current_offset - req->current_input->start;
191     if(memcmp(req->current_input->buff + req->current_input->start,
192               REQ_PAT + (REQ_PATSIZE - inset), inset) == 0 &&
193        memcmp(req->current_input->prev->buff +
194                 req->current_input->prev->start +
195                 req->current_input->prev->size - REQ_PATSIZE + inset,
196               REQ_PAT + inset,
197               REQ_PATSIZE - inset) == 0) goto match;
198   }
199   start = MAX(req->current_offset - REQ_PATSIZE, req->current_input->start);
200   mstr = strnstrn(REQ_PAT, REQ_PATSIZE,
201                   req->current_input->buff + start,
202                   req->current_input->size -
203                     (start - req->current_input->start));
204   if(!mstr && req->current_input->next) {
205     req->current_input = req->current_input->next;
206     req->current_offset = req->current_input->start;
207     goto restart;
208   }
209   if(!mstr) return false;
210   req->current_offset = mstr - req->current_input->buff + REQ_PATSIZE;
211  match:
212   req->current_request_chain = req->first_input;
213   if(req->current_offset <
214      req->current_input->start + req->current_input->size) {
215     /* There are left-overs */
216     req->first_input = bchain_alloc(MAX(DEFAULT_BCHAINSIZE,
217                                         req->current_input->size));
218     req->first_input->prev = NULL;
219     req->first_input->next = req->current_input->next;
220     req->first_input->start = 0;
221     req->first_input->size = req->current_input->size - (req->current_offset + req->current_input->start);
222     memcpy(req->first_input->buff,
223            req->current_input->buff +
224              req->current_input->start + req->current_offset,
225            req->first_input->size);
226     if(req->last_input == req->current_request_chain)
227       req->last_input = req->first_input;
228   }
229   else {
230     req->first_input = req->last_input = NULL;
231   }
232   req->current_input = NULL;
233   req->current_offset = 0;
234
235   /* Now we need to dissect the current_request_chain into an HTTP request */
236   /* First step: make sure that no line crosses a chain boundary by
237    * inserting new chains as necessary.
238    */
239   if(!_fixup_bchain(req->current_request_chain)) {
240     *err = noit_true;
241     return noit_false;
242   }
243   /* Second step is to parse out the request itself */
244   for(b = req->current_request_chain; b; b = b->next) {
245     char *curr_str, *next_str;
246     b->buff[b->start + b->size - 2] = '\0';
247     curr_str = b->buff + b->start;
248     do {
249       next_str = strstr(curr_str, "\r\n");
250       if(next_str) {
251         *((char *)next_str) = '\0';
252         next_str += 2;
253       }
254       if(*curr_str == '\0') break; /* our CRLFCRLF... end of req */
255 #define FAIL do { *err = noit_true; return noit_false; } while(0)
256       if(!req->method_str) { /* request line */
257         req->method_str = (char *)curr_str;
258         req->uri_str = strchr(curr_str, ' ');
259         if(!req->uri_str) FAIL;
260         *(req->uri_str) = '\0';
261         req->uri_str++;
262         req->protocol_str = strchr(req->uri_str, ' ');
263         if(!req->protocol_str) FAIL;
264         *(req->protocol_str) = '\0';
265         req->protocol_str++;
266         req->method = _method_enum(req->method_str);
267         req->protocol = _protocol_enum(req->protocol_str);
268       }
269       else { /* request headers */
270         const char *name, *value;
271         if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
272         if(!name && !last_name) FAIL;
273         if(name)
274           noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
275                             NULL, NULL);
276         else {
277           struct bchain *b;
278           const char *prefix = NULL;
279           int l1, l2;
280           noit_hash_retrieve(&req->headers, last_name, strlen(last_name),
281                              (void **)&prefix);
282           if(!prefix) FAIL;
283           l1 = strlen(prefix);
284           l2 = strlen(value);
285           b = bchain_alloc(l1 + l2 + 2);
286           b->next = req->current_request_chain;
287           b->next->prev = b;
288           req->current_request_chain = b;
289           b->size = l1 + l2 + 2;
290           memcpy(b->buff, prefix, l1);
291           b->buff[l1] = ' ';
292           memcpy(b->buff + l1 + 1, value, l2);
293           b->buff[l1 + 1 + l2] = '\0';
294           noit_hash_replace(&req->headers, last_name, strlen(last_name),
295                             b->buff, NULL, NULL);
296         }
297         if(name) last_name = name;
298       }
299       curr_str = next_str;
300     } while(next_str);
301   }
302   req->complete = noit_true;
303   return noit_true;
304 }
305 int
306 noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
307   struct bchain *in;
308   noit_boolean rv, err;
309
310   if(mask & EVENTER_EXCEPTION) {
311    full_error:
312     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
313     ctx->conn.e = NULL;
314     return 0;
315   }
316   if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
317
318   /* We could have a complete request in the tail of a previous request */
319   rv = noit_http_request_finalize(&ctx->req, &err);
320   if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
321   if(err == noit_true) goto full_error;
322
323   in = ctx->req.last_input;
324   if(!in) {
325     in = ctx->req.first_input = ctx->req.last_input =
326       bchain_alloc(DEFAULT_BCHAINSIZE);
327     if(!in) goto full_error;
328   }
329   while(1) {
330     int len;
331
332     if(in->size > 0 && /* we've read something */
333        DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
334        DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
335       in->next = ctx->req.last_input =
336         bchain_alloc(DEFAULT_BCHAINSIZE);
337       in->next->prev = in;
338       in = in->next;
339       if(!in) goto full_error;
340     }
341
342     len = ctx->conn.e->opset->read(ctx->conn.e->fd,
343                                    in->buff + in->start + in->size,
344                                    in->allocd - in->size - in->start,
345                                    &mask, ctx->conn.e);
346     if(len == -1 && errno == EAGAIN) return mask;
347     if(len <= 0) goto full_error;
348     if(len > 0) in->size += len;
349     rv = noit_http_request_finalize(&ctx->req, &err);
350     if(len == -1 || err == noit_true) {
351       goto full_error;
352     }
353     if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
354   }
355   return EVENTER_READ | EVENTER_EXCEPTION;
356 }
357
358 int
359 noit_http_session_drive(eventer_t e, int mask, void *closure,
360                         struct timeval *now) {
361   noit_http_session_ctx *ctx = closure;
362   int rv;
363  next_req:
364   if(ctx->req.complete != noit_true) {
365     mask = noit_http_complete_request(ctx, mask);
366     if(ctx->req.complete != noit_true) return mask;
367   }
368   rv = ctx->dispatcher(ctx);
369   _http_perform_write(ctx, &mask);
370   if(ctx->res.complete == noit_true &&
371      ctx->conn.e &&
372      ctx->conn.needs_close == noit_true) {
373     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
374     ctx->conn.e = NULL;
375     return 0;
376   }
377   if(ctx->req.complete == noit_false) goto next_req;
378   if(ctx->conn.e) {
379     return mask;
380   }
381   return 0;
382 }
383
384 noit_http_session_ctx *
385 noit_http_session_ctx_new(noit_http_dispatch_func f, eventer_t e) {
386   noit_http_session_ctx *ctx;
387   ctx = calloc(1, sizeof(*ctx));
388   ctx->req.complete = noit_false;
389   ctx->conn.e = e;
390   ctx->dispatcher = f;
391   ctx->drive = noit_http_session_drive;
392   return ctx;
393 }
394
395 noit_boolean
396 noit_http_response_status_set(noit_http_session_ctx *ctx,
397                               int code, const char *reason) {
398   if(ctx->res.output_started == noit_true) return noit_false;
399   ctx->res.protocol = ctx->req.protocol;
400   if(code < 100 || code > 999) return noit_false;
401   ctx->res.status_code = code;
402   if(ctx->res.status_reason) free(ctx->res.status_reason);
403   ctx->res.status_reason = strdup(reason);
404   return noit_true;
405 }
406 noit_boolean
407 noit_http_response_header_set(noit_http_session_ctx *ctx,
408                               const char *name, const char *value) {
409   if(ctx->res.output_started == noit_true) return noit_false;
410   noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
411                     strdup(value), free, free);
412   return noit_true;
413 }
414 noit_boolean
415 noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
416   if(ctx->res.output_started == noit_true) return noit_false;
417   /* transfer and content encodings only allowed in HTTP/1.1 */
418   if(ctx->res.protocol != NOIT_HTTP11 &&
419      (opt & NOIT_HTTP_CHUNKED))
420     return noit_false;
421   if(ctx->res.protocol != NOIT_HTTP11 &&
422      (opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
423     return noit_false;
424   if(((ctx->res.output_options | opt) &
425       (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
426         (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
427     return noit_false;
428   ctx->res.output_options |= opt;
429   if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
430     CTX_ADD_HEADER("Transfer-Encoding", "chunked");
431   if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
432     CTX_ADD_HEADER("Vary", "Accept-Encoding");
433     if(ctx->res.output_options & NOIT_HTTP_GZIP)
434       CTX_ADD_HEADER("Content-Encoding", "gzip");
435     else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
436       CTX_ADD_HEADER("Content-Encoding", "deflate");
437   }
438   if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
439     CTX_ADD_HEADER("Connection", "close");
440     ctx->conn.needs_close = noit_true;
441   }
442   return noit_true;
443 }
444 noit_boolean
445 noit_http_response_append(noit_http_session_ctx *ctx,
446                           const void *b, size_t l) {
447   struct bchain *o;
448   int boff = 0;
449   if(ctx->res.closed == noit_true) return noit_false;
450   if(ctx->res.output_started == noit_true &&
451      !(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
452     return noit_false;
453   if(!ctx->res.output)
454     ctx->res.output = bchain_alloc(DEFAULT_BCHAINSIZE);
455   o = ctx->res.output;
456   while(o->next) o = o->next;
457   while(l > 0) {
458     if(o->allocd == o->start + o->size) {
459       /* Filled up, need another */
460       o->next = bchain_alloc(DEFAULT_BCHAINSIZE);
461       o->next->prev = o->next;
462       o = o->next;
463     }
464     if(o->allocd > o->start + o->size) {
465       int tocopy = MIN(l, o->allocd - o->start - o->size);
466       memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
467       o->size += tocopy;
468       boff += tocopy;
469       l -= tocopy;
470     }
471   }
472   return noit_true;
473 }
474 noit_boolean
475 noit_http_response_append_bchain(noit_http_session_ctx *ctx,
476                                  struct bchain *b) {
477   struct bchain *o;
478   if(ctx->res.closed == noit_true) return noit_false;
479   if(ctx->res.output_started == noit_true &&
480      !(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
481     return noit_false;
482   if(!ctx->res.output)
483     ctx->res.output = b;
484   else {
485     o = ctx->res.output;
486     while(o->next) o = o->next;
487     o->next = b;
488     b->prev = o;
489   }
490   return noit_true;
491 }
492 static int
493 _http_construct_leader(noit_http_session_ctx *ctx) {
494   int len = 0, tlen;
495   struct bchain *b;
496   const char *protocol_str;
497   const char *key, *value;
498   int klen;
499   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
500
501   assert(!ctx->res.leader);
502   ctx->res.leader = b = bchain_alloc(DEFAULT_BCHAINSIZE);
503
504   protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
505                    "HTTP/1.1" :
506                    (ctx->res.protocol == NOIT_HTTP10 ?
507                      "HTTP/1.0" :
508                      "HTTP/0.9");
509   tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
510                   protocol_str, ctx->res.status_code, ctx->res.status_reason);
511   if(tlen < 0) return -1;
512   len = b->size = tlen;
513
514 #define CTX_LEADER_APPEND(s, slen) do { \
515   if(b->size + slen > DEFAULT_BCHAINSIZE) { \
516     b->next = bchain_alloc(DEFAULT_BCHAINSIZE); \
517     assert(b->next); \
518     b->next->prev = b; \
519     b = b->next; \
520   } \
521   assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
522   memcpy(b->buff + b->start + b->size, s, slen); \
523   b->size += slen; \
524 } while(0)
525   while(noit_hash_next(&ctx->res.headers, &iter,
526                        &key, &klen, (void **)&value)) {
527     int vlen = strlen(value);
528     CTX_LEADER_APPEND(key, klen);
529     CTX_LEADER_APPEND(": ", 2);
530     CTX_LEADER_APPEND(value, vlen);
531     CTX_LEADER_APPEND("\r\n", 2);
532   }
533   CTX_LEADER_APPEND("\r\n", 2);
534   return len;
535 }
536 static noit_boolean
537 _http_encode_chain(struct bchain *out, struct bchain *in, int opts) {
538   /* implement gzip and deflate! */
539   if(opts & NOIT_HTTP_GZIP) {
540   }
541   else if(opts & NOIT_HTTP_DEFLATE) {
542   }
543   else {
544     if(in->size > out->allocd - out->start) return noit_false;
545     memcpy(out->buff + out->start, in->buff + in->start, in->size);
546     out->size += in->size;
547   }
548   return noit_true;
549 }
550 struct bchain *
551 noit_http_process_output_bchain(noit_http_session_ctx *ctx,
552                                 struct bchain *in) {
553   struct bchain *out;
554   int ilen, hexlen;
555   int opts = ctx->res.output_options;
556
557   /* a chunked header looks like: hex*\r\ndata\r\n */
558   /* let's assume that content never gets "larger" */
559   /* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
560   ilen = in->size;
561   hexlen = 0;
562   while(ilen) { ilen >>= 4; hexlen++; }
563   if(hexlen == 0) hexlen = 1;
564
565   out = bchain_alloc(hexlen + 4 + in->size);
566   /* if we're chunked, let's give outselved hexlen + 2 prefix space */
567   if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
568   if(_http_encode_chain(out, in, opts) == noit_false) {
569     free(out);
570     return NULL;
571   }
572   /* Too long! Out "larger" assumption is bad */
573   if(opts & NOIT_HTTP_CHUNKED) {
574     ilen = out->size;
575     assert(out->start+out->size+2 <= out->allocd);
576     out->buff[out->start + out->size++] = '\r';
577     out->buff[out->start + out->size++] = '\n';
578     out->start = 0;
579     /* terminate */
580     out->size += 2;
581     out->buff[hexlen] = '\r';
582     out->buff[hexlen+1] = '\n';
583     /* backfill */
584     out->size += hexlen;
585     while(hexlen > 0) {
586       out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
587       ilen >>= 4;
588       hexlen--;
589     }
590   }
591   return out;
592 }
593 noit_boolean
594 noit_http_response_flush(noit_http_session_ctx *ctx, noit_boolean final) {
595   struct bchain *o, *r;
596   int mask;
597
598   if(ctx->res.closed == noit_true) return noit_false;
599   if(ctx->res.output_started == noit_false) {
600     _http_construct_leader(ctx);
601     ctx->res.output_started = noit_true;
602   }
603   /* encode output to output_raw */
604   r = ctx->res.output_raw;
605   while(r && r->next) r = r->next;
606   /* r is the last raw output link */
607   o = ctx->res.output;
608   /* o is the first output link to process */
609   while(o) {
610     struct bchain *tofree, *n;
611     n = noit_http_process_output_bchain(ctx, o);
612     if(!n) {
613       /* Bad, response stops here! */
614       noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
615       while(o) { tofree = o; o = o->next; free(tofree); }
616       final = noit_true;
617       break;
618     }
619     if(r) {
620       r->next = n;
621       n->prev = r;
622       r = n;
623     }
624     else {
625       r = ctx->res.output_raw = n;
626     }
627     tofree = o; o = o->next; free(tofree); /* advance and free */
628   }
629   ctx->res.output = NULL;
630   if(final) {
631     struct bchain *n;
632     ctx->res.closed = noit_true;
633     if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
634       n = bchain_from_data("0\r\n\r\n", 5);
635     else
636       n = bchain_from_data("\r\n", 2);
637     if(r) {
638       r->next = n;
639       n->prev = r;
640       r = n;
641     }
642     else {
643       r = ctx->res.output_raw = n;
644     }
645   }
646
647   _http_perform_write(ctx, &mask);
648   if(ctx->conn.e) {
649     ctx->conn.e->mask = mask;
650     eventer_update(ctx->conn.e);
651   }
652   return noit_true;
653 }
654 noit_boolean
655 noit_http_response_end(noit_http_session_ctx *ctx) {
656   if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
657   return noit_true;
658 }
Note: See TracBrowser for help on using the browser.