ext: fix memory leak when reentrant/multithreaded
[clogger.git] / ext / clogger_ext / clogger.c
blob6e1493864f8368942fb788cbbd0e3cc49d00b9b1
1 #define _BSD_SOURCE
2 #include <ruby.h>
3 #include <assert.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/time.h>
7 #include <time.h>
8 #include <errno.h>
9 #ifdef HAVE_FCNTL_H
10 # include <fcntl.h>
11 #endif
12 #include "ruby_1_9_compat.h"
14 /* in case _BSD_SOURCE doesn't give us this macro */
15 #ifndef timersub
16 # define timersub(a, b, result) \
17 do { \
18 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
19 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
20 if ((result)->tv_usec < 0) { \
21 --(result)->tv_sec; \
22 (result)->tv_usec += 1000000; \
23 } \
24 } while (0)
25 #endif
27 /* give GCC hints for better branch prediction
28 * (we layout branches so that ASCII characters are handled faster) */
29 #if defined(__GNUC__) && (__GNUC__ >= 3)
30 # define likely(x) __builtin_expect (!!(x), 1)
31 # define unlikely(x) __builtin_expect (!!(x), 0)
32 #else
33 # define unlikely(x) (x)
34 # define likely(x) (x)
35 #endif
37 enum clogger_opcode {
38 CL_OP_LITERAL = 0,
39 CL_OP_REQUEST,
40 CL_OP_RESPONSE,
41 CL_OP_SPECIAL,
42 CL_OP_EVAL,
43 CL_OP_TIME_LOCAL,
44 CL_OP_TIME_UTC,
45 CL_OP_REQUEST_TIME,
46 CL_OP_TIME,
47 CL_OP_COOKIE
50 enum clogger_special {
51 CL_SP_body_bytes_sent = 0,
52 CL_SP_status,
53 CL_SP_request,
54 CL_SP_request_length,
55 CL_SP_response_length,
56 CL_SP_ip,
57 CL_SP_pid,
58 CL_SP_request_uri
61 struct clogger {
62 VALUE app;
64 VALUE fmt_ops;
65 VALUE logger;
66 VALUE log_buf;
68 VALUE env;
69 VALUE cookies;
70 VALUE status;
71 VALUE headers;
72 VALUE body;
74 off_t body_bytes_sent;
75 struct timeval tv_start;
77 int fd;
78 int wrap_body;
79 int need_resp;
80 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
83 static ID ltlt_id;
84 static ID call_id;
85 static ID each_id;
86 static ID close_id;
87 static ID to_i_id;
88 static ID to_s_id;
89 static ID size_id;
90 static ID sq_brace_id;
91 static ID new_id;
92 static VALUE cClogger;
93 static VALUE mFormat;
94 static VALUE cHeaderHash;
96 /* common hash lookup keys */
97 static VALUE g_HTTP_X_FORWARDED_FOR;
98 static VALUE g_REMOTE_ADDR;
99 static VALUE g_REQUEST_METHOD;
100 static VALUE g_PATH_INFO;
101 static VALUE g_REQUEST_URI;
102 static VALUE g_QUERY_STRING;
103 static VALUE g_HTTP_VERSION;
104 static VALUE g_rack_errors;
105 static VALUE g_rack_input;
106 static VALUE g_rack_multithread;
107 static VALUE g_dash;
108 static VALUE g_empty;
109 static VALUE g_space;
110 static VALUE g_question_mark;
111 static VALUE g_rack_request_cookie_hash;
113 #define LOG_BUF_INIT_SIZE 128
115 static void init_buffers(struct clogger *c)
117 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
120 static inline int need_escape(unsigned c)
122 assert(c <= 0xff);
123 return !!(c == '\'' || c == '"' || (c >= 0 && c <= 0x1f));
126 /* we are encoding-agnostic, clients can send us all sorts of junk */
127 static VALUE byte_xs_str(VALUE from)
129 static const char esc[] = "0123456789ABCDEF";
130 unsigned char *new_ptr;
131 unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
132 long len = RSTRING_LEN(from);
133 long new_len = len;
134 VALUE rv;
136 for (; --len >= 0; ptr++) {
137 unsigned c = *ptr;
139 if (unlikely(need_escape(c)))
140 new_len += 3; /* { '\', 'x', 'X', 'X' } */
143 len = RSTRING_LEN(from);
144 if (new_len == len)
145 return from;
147 rv = rb_str_new(0, new_len);
148 new_ptr = (unsigned char *)RSTRING_PTR(rv);
149 ptr = (unsigned char *)RSTRING_PTR(from);
150 for (; --len >= 0; ptr++) {
151 unsigned c = *ptr;
153 if (unlikely(need_escape(c))) {
154 *new_ptr++ = '\\';
155 *new_ptr++ = 'x';
156 *new_ptr++ = esc[c >> 4];
157 *new_ptr++ = esc[c & 0xf];
158 } else {
159 *new_ptr++ = c;
162 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
164 return rv;
167 static VALUE byte_xs(VALUE from)
169 return byte_xs_str(rb_obj_as_string(from));
172 static void clogger_mark(void *ptr)
174 struct clogger *c = ptr;
176 rb_gc_mark(c->app);
177 rb_gc_mark(c->fmt_ops);
178 rb_gc_mark(c->logger);
179 rb_gc_mark(c->log_buf);
180 rb_gc_mark(c->env);
181 rb_gc_mark(c->cookies);
182 rb_gc_mark(c->status);
183 rb_gc_mark(c->headers);
184 rb_gc_mark(c->body);
187 static VALUE clogger_alloc(VALUE klass)
189 struct clogger *c;
191 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
194 static struct clogger *clogger_get(VALUE self)
196 struct clogger *c;
198 Data_Get_Struct(self, struct clogger, c);
199 assert(c);
200 return c;
203 static VALUE obj_fileno(VALUE obj)
205 return rb_funcall(obj, rb_intern("fileno"), 0);
208 static VALUE obj_enable_sync(VALUE obj)
210 return rb_funcall(obj, rb_intern("sync="), 1, Qtrue);
213 /* only for writing to regular files, not stupid crap like NFS */
214 static void write_full(int fd, const void *buf, size_t count)
216 ssize_t r;
218 while (count > 0) {
219 r = write(fd, buf, count);
221 if (r == count) { /* overwhelmingly likely */
222 return;
223 } else if (r > 0) {
224 count -= r;
225 buf += r;
226 } else {
227 if (errno == EINTR || errno == EAGAIN)
228 continue; /* poor souls on NFS and like: */
229 if (!errno)
230 errno = ENOSPC;
231 rb_sys_fail("write");
237 * allow us to use write_full() iff we detect a blocking file
238 * descriptor that wouldn't play nicely with Ruby threading/fibers
240 static int raw_fd(VALUE fileno)
242 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
243 int fd;
244 int flags;
246 if (NIL_P(fileno))
247 return -1;
248 fd = NUM2INT(fileno);
250 flags = fcntl(fd, F_GETFL);
251 if (flags < 0)
252 rb_sys_fail("fcntl");
254 return (flags & O_NONBLOCK) ? -1 : fd;
255 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
256 return -1;
257 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
260 /* :nodoc: */
261 static VALUE clogger_reentrant(VALUE self)
263 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
266 /* :nodoc: */
267 static VALUE clogger_wrap_body(VALUE self)
269 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
272 static void append_status(struct clogger *c)
274 char buf[sizeof("999")];
275 int nr;
276 VALUE status = c->status;
278 if (TYPE(status) != T_FIXNUM) {
279 status = rb_funcall(status, to_i_id, 0);
280 /* no way it's a valid status code (at least not HTTP/1.1) */
281 if (TYPE(status) != T_FIXNUM) {
282 rb_str_buf_append(c->log_buf, g_dash);
283 return;
287 nr = NUM2INT(status);
288 if (nr >= 100 && nr <= 999) {
289 nr = snprintf(buf, sizeof(buf), "%03d", nr);
290 assert(nr == 3);
291 rb_str_buf_cat(c->log_buf, buf, nr);
292 } else {
293 /* raise?, swap for 500? */
294 rb_str_buf_append(c->log_buf, g_dash);
298 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
299 static void append_ip(struct clogger *c)
301 VALUE env = c->env;
302 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
304 if (NIL_P(tmp)) {
305 /* can't be faked on any real server, so no escape */
306 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
307 if (NIL_P(tmp))
308 tmp = g_dash;
309 } else {
310 tmp = byte_xs(tmp);
312 rb_str_buf_append(c->log_buf, tmp);
315 static void append_body_bytes_sent(struct clogger *c)
317 char buf[(sizeof(off_t) * 8) / 3 + 1];
318 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
319 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
321 assert(nr > 0 && nr < sizeof(buf));
322 rb_str_buf_cat(c->log_buf, buf, nr);
325 static void append_tv(struct clogger *c, const VALUE *op, struct timeval *tv)
327 char buf[sizeof(".000000") + ((sizeof(tv->tv_sec) * 8) / 3)];
328 int nr;
329 char *fmt = RSTRING_PTR(op[1]);
330 int div = NUM2INT(op[2]);
332 nr = snprintf(buf, sizeof(buf), fmt,
333 (int)tv->tv_sec, (int)(tv->tv_usec / div));
334 assert(nr > 0 && nr < sizeof(buf));
335 rb_str_buf_cat(c->log_buf, buf, nr);
338 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
340 struct timeval now, d;
342 gettimeofday(&now, NULL);
343 timersub(&now, &c->tv_start, &d);
344 append_tv(c, op, &d);
347 static void append_time_fmt(struct clogger *c, const VALUE *op)
349 struct timeval now;
351 gettimeofday(&now, NULL);
352 append_tv(c, op, &now);
355 static void append_request_uri(struct clogger *c)
357 VALUE tmp;
359 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
360 if (NIL_P(tmp)) {
361 tmp = rb_hash_aref(c->env, g_PATH_INFO);
362 if (!NIL_P(tmp))
363 rb_str_buf_append(c->log_buf, byte_xs(tmp));
364 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
365 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
366 rb_str_buf_append(c->log_buf, g_question_mark);
367 rb_str_buf_append(c->log_buf, byte_xs(tmp));
369 } else {
370 rb_str_buf_append(c->log_buf, byte_xs(tmp));
374 static void append_request(struct clogger *c)
376 VALUE tmp;
378 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
379 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
380 if (!NIL_P(tmp))
381 rb_str_buf_append(c->log_buf, tmp);
383 rb_str_buf_append(c->log_buf, g_space);
385 append_request_uri(c);
387 /* HTTP_VERSION can be injected by malicious clients */
388 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
389 if (!NIL_P(tmp)) {
390 rb_str_buf_append(c->log_buf, g_space);
391 rb_str_buf_append(c->log_buf, byte_xs(tmp));
395 static void append_request_length(struct clogger *c)
397 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
398 if (NIL_P(tmp)) {
399 rb_str_buf_append(c->log_buf, g_dash);
400 } else {
401 tmp = rb_funcall(tmp, size_id, 0);
402 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
406 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
408 /* you'd have to be a moron to use formats this big... */
409 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
410 size_t nr;
411 struct tm tmp;
412 time_t t = time(NULL);
414 if (op == CL_OP_TIME_LOCAL)
415 localtime_r(&t, &tmp);
416 else if (op == CL_OP_TIME_UTC)
417 gmtime_r(&t, &tmp);
418 else
419 assert(0 && "unknown op");
421 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
422 if (nr == 0 || nr == sizeof(buf))
423 rb_str_buf_append(c->log_buf, g_dash);
424 else
425 rb_str_buf_cat(c->log_buf, buf, nr);
428 static void append_pid(struct clogger *c)
430 char buf[(sizeof(pid_t) * 8) / 3 + 1];
431 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
433 assert(nr > 0 && nr < sizeof(buf));
434 rb_str_buf_cat(c->log_buf, buf, nr);
437 static void append_eval(struct clogger *c, VALUE str)
439 int state = -1;
440 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
442 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
443 rb_str_buf_append(c->log_buf, rv);
446 static void append_cookie(struct clogger *c, VALUE key)
448 VALUE cookie;
450 if (c->cookies == Qfalse)
451 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
453 if (NIL_P(c->cookies)) {
454 cookie = g_dash;
455 } else {
456 cookie = rb_hash_aref(c->cookies, key);
457 if (NIL_P(cookie))
458 cookie = g_dash;
460 rb_str_buf_append(c->log_buf, cookie);
463 static void append_request_env(struct clogger *c, VALUE key)
465 VALUE tmp = rb_hash_aref(c->env, key);
467 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
468 rb_str_buf_append(c->log_buf, tmp);
471 static void append_response(struct clogger *c, VALUE key)
473 VALUE v;
475 assert(rb_obj_class(c->headers) == cHeaderHash);
477 v = rb_funcall(c->headers, sq_brace_id, 1, key);
478 v = NIL_P(v) ? g_dash : byte_xs(v);
479 rb_str_buf_append(c->log_buf, v);
482 static void special_var(struct clogger *c, enum clogger_special var)
484 switch (var) {
485 case CL_SP_body_bytes_sent:
486 append_body_bytes_sent(c);
487 break;
488 case CL_SP_status:
489 append_status(c);
490 break;
491 case CL_SP_request:
492 append_request(c);
493 break;
494 case CL_SP_request_length:
495 append_request_length(c);
496 break;
497 case CL_SP_response_length:
498 if (c->body_bytes_sent == 0)
499 rb_str_buf_append(c->log_buf, g_dash);
500 else
501 append_body_bytes_sent(c);
502 break;
503 case CL_SP_ip:
504 append_ip(c);
505 break;
506 case CL_SP_pid:
507 append_pid(c);
508 break;
509 case CL_SP_request_uri:
510 append_request_uri(c);
514 static VALUE cwrite(struct clogger *c)
516 const VALUE ops = c->fmt_ops;
517 const VALUE *ary = RARRAY_PTR(ops);
518 long i = RARRAY_LEN(ops);
519 VALUE dst = c->log_buf;
521 rb_str_set_len(dst, 0);
523 for (; --i >= 0; ary++) {
524 const VALUE *op = RARRAY_PTR(*ary);
525 enum clogger_opcode opcode = NUM2INT(op[0]);
527 switch (opcode) {
528 case CL_OP_LITERAL:
529 rb_str_buf_append(dst, op[1]);
530 break;
531 case CL_OP_REQUEST:
532 append_request_env(c, op[1]);
533 break;
534 case CL_OP_RESPONSE:
535 append_response(c, op[1]);
536 break;
537 case CL_OP_SPECIAL:
538 special_var(c, NUM2INT(op[1]));
539 break;
540 case CL_OP_EVAL:
541 append_eval(c, op[1]);
542 break;
543 case CL_OP_TIME_LOCAL:
544 case CL_OP_TIME_UTC:
545 append_time(c, opcode, op[1]);
546 break;
547 case CL_OP_REQUEST_TIME:
548 append_request_time_fmt(c, op);
549 break;
550 case CL_OP_TIME:
551 append_time_fmt(c, op);
552 break;
553 case CL_OP_COOKIE:
554 append_cookie(c, op[1]);
555 break;
559 if (c->fd >= 0) {
560 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
561 } else {
562 VALUE logger = c->logger;
564 if (NIL_P(logger))
565 logger = rb_hash_aref(c->env, g_rack_errors);
566 rb_funcall(logger, ltlt_id, 1, dst);
569 return Qnil;
573 * call-seq:
574 * Clogger.new(app, :logger => $stderr, :format => string) => obj
576 * Creates a new Clogger object that wraps +app+. +:logger+ may
577 * be any object that responds to the "<<" method with a string argument.
579 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
581 struct clogger *c = clogger_get(self);
582 VALUE o = Qnil;
583 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
585 rb_scan_args(argc, argv, "11", &c->app, &o);
586 c->fd = -1;
587 c->logger = Qnil;
588 c->reentrant = -1; /* auto-detect */
590 if (TYPE(o) == T_HASH) {
591 VALUE tmp;
593 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
594 if (!NIL_P(c->logger)) {
595 rb_rescue(obj_enable_sync, c->logger, 0, 0);
596 c->fd = raw_fd(rb_rescue(obj_fileno, c->logger, 0, 0));
599 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
600 if (!NIL_P(tmp))
601 fmt = tmp;
603 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
604 switch (TYPE(tmp)) {
605 case T_TRUE:
606 c->reentrant = 1;
607 break;
608 case T_FALSE:
609 c->reentrant = 0;
610 case T_NIL:
611 break;
612 default:
613 rb_raise(rb_eArgError, ":reentrant must be boolean");
617 init_buffers(c);
618 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
620 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
621 1, c->fmt_ops))
622 c->need_resp = 1;
623 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
624 1, c->fmt_ops))
625 c->wrap_body = 1;
627 return self;
630 static VALUE body_iter_i(VALUE str, VALUE memop)
632 off_t *len = (off_t *)memop;
634 str = rb_obj_as_string(str);
635 *len += RSTRING_LEN(str);
637 return rb_yield(str);
640 static VALUE wrap_each(struct clogger *c)
642 c->body_bytes_sent = 0;
643 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
645 return c->body;
649 * call-seq:
650 * clogger.each { |part| socket.write(part) }
652 * Delegates the body#each call to the underlying +body+ object
653 * while tracking the number of bytes yielded. This will log
654 * the request.
656 static VALUE clogger_each(VALUE self)
658 struct clogger *c = clogger_get(self);
660 rb_need_block();
662 return rb_ensure(wrap_each, (VALUE)c, cwrite, (VALUE)c);
666 * call-seq:
667 * clogger.close
669 * Delegates the body#close call to the underlying +body+ object.
670 * This is only used when Clogger is wrapping the +body+ of a Rack
671 * response and should be automatically called by the web server.
673 static VALUE clogger_close(VALUE self)
675 struct clogger *c = clogger_get(self);
677 if (rb_respond_to(c->body, close_id))
678 return rb_funcall(c->body, close_id, 0);
679 return Qnil;
682 /* :nodoc: */
683 static VALUE clogger_fileno(VALUE self)
685 struct clogger *c = clogger_get(self);
687 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
690 static VALUE ccall(struct clogger *c, VALUE env)
692 VALUE rv;
694 gettimeofday(&c->tv_start, NULL);
695 c->env = env;
696 c->cookies = Qfalse;
697 rv = rb_funcall(c->app, call_id, 1, env);
698 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
699 VALUE *tmp = RARRAY_PTR(rv);
701 c->status = tmp[0];
702 c->headers = tmp[1];
703 c->body = tmp[2];
705 rv = rb_ary_new4(3, tmp);
706 if (c->need_resp && cHeaderHash != rb_obj_class(c->headers)) {
707 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
708 rb_ary_store(rv, 1, c->headers);
710 } else {
711 c->status = INT2NUM(500);
712 c->headers = c->body = rb_ary_new();
713 cwrite(c);
714 rb_raise(rb_eTypeError,
715 "app response not a 3 element Array: %s",
716 RSTRING_PTR(rb_inspect(rv)));
719 return rv;
723 * call-seq:
724 * clogger.call(env) => [ status, headers, body ]
726 * calls the wrapped Rack application with +env+, returns the
727 * [status, headers, body ] tuplet required by Rack.
729 static VALUE clogger_call(VALUE self, VALUE env)
731 struct clogger *c = clogger_get(self);
732 VALUE rv;
734 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
736 if (c->wrap_body) {
737 if (c->reentrant < 0) {
738 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
739 c->reentrant = Qfalse == tmp ? 0 : 1;
741 if (c->reentrant) {
742 self = rb_obj_dup(self);
743 c = clogger_get(self);
746 rv = ccall(c, env);
747 assert(!OBJ_FROZEN(rv) && "frozen response array");
748 rb_ary_store(rv, 2, self);
750 return rv;
753 rv = ccall(c, env);
754 cwrite(c);
756 return rv;
759 /* :nodoc */
760 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
762 struct clogger *a = clogger_get(orig);
763 struct clogger *b = clogger_get(clone);
765 memcpy(b, a, sizeof(struct clogger));
766 init_buffers(b);
768 return clone;
771 #define CONST_GLOBAL_STR2(var, val) do { \
772 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
773 rb_global_variable(&g_##var); \
774 } while (0)
776 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
778 static void init_rack_utils_header_hash(void)
780 VALUE mRack, mUtils;
781 #if 0
782 extra double quotes below are to disable rdoc (and so is avoiding comments)
783 let me know if there is a better way...
784 #endif
785 rb_require("rack");
786 mRack = rb_define_module("Rack""");
787 mUtils = rb_define_module_under(mRack, "Utils""");
788 cHeaderHash = rb_define_class_under(mUtils, "HeaderHash""", rb_cHash);
791 void Init_clogger_ext(void)
793 ltlt_id = rb_intern("<<");
794 call_id = rb_intern("call");
795 each_id = rb_intern("each");
796 close_id = rb_intern("close");
797 to_i_id = rb_intern("to_i");
798 to_s_id = rb_intern("to_s");
799 size_id = rb_intern("size");
800 sq_brace_id = rb_intern("[]");
801 new_id = rb_intern("new");
802 cClogger = rb_define_class("Clogger", rb_cObject);
803 mFormat = rb_define_module_under(cClogger, "Format");
804 rb_define_alloc_func(cClogger, clogger_alloc);
805 rb_define_method(cClogger, "initialize", clogger_init, -1);
806 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
807 rb_define_method(cClogger, "call", clogger_call, 1);
808 rb_define_method(cClogger, "each", clogger_each, 0);
809 rb_define_method(cClogger, "close", clogger_close, 0);
810 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
811 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
812 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
813 CONST_GLOBAL_STR(REMOTE_ADDR);
814 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
815 CONST_GLOBAL_STR(REQUEST_METHOD);
816 CONST_GLOBAL_STR(PATH_INFO);
817 CONST_GLOBAL_STR(QUERY_STRING);
818 CONST_GLOBAL_STR(REQUEST_URI);
819 CONST_GLOBAL_STR(HTTP_VERSION);
820 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
821 CONST_GLOBAL_STR2(rack_input, "rack.input");
822 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
823 CONST_GLOBAL_STR2(dash, "-");
824 CONST_GLOBAL_STR2(empty, "");
825 CONST_GLOBAL_STR2(space, " ");
826 CONST_GLOBAL_STR2(question_mark, "?");
827 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
828 init_rack_utils_header_hash();