eb1605bac068d805f056ab6ea2aa67c03da3d8ef
[clogger.git] / ext / clogger_ext / clogger.c
blobeb1605bac068d805f056ab6ea2aa67c03da3d8ef
1 #include <ruby.h>
2 #ifdef HAVE_RUBY_IO_H
3 # include <ruby/io.h>
4 #else
5 # include <rubyio.h>
6 #endif
7 #include <assert.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <errno.h>
13 #ifdef HAVE_FCNTL_H
14 # include <fcntl.h>
15 #endif
16 #ifndef _POSIX_C_SOURCE
17 # define _POSIX_C_SOURCE 200112L
18 #endif
19 #include <time.h>
20 #include <stdlib.h>
21 #include "ruby_1_9_compat.h"
22 #include "broken_system_compat.h"
23 #include "blocking_helpers.h"
26 * Availability of a monotonic clock needs to be detected at runtime
27 * since we could've been built on a different system than we're run
28 * under.
30 static clockid_t hopefully_CLOCK_MONOTONIC;
32 static void check_clock(void)
34 struct timespec now;
36 hopefully_CLOCK_MONOTONIC = CLOCK_MONOTONIC;
38 /* we can't check this reliably at compile time */
39 if (clock_gettime(CLOCK_MONOTONIC, &now) == 0)
40 return;
42 if (clock_gettime(CLOCK_REALTIME, &now) == 0) {
43 hopefully_CLOCK_MONOTONIC = CLOCK_REALTIME;
44 rb_warn("CLOCK_MONOTONIC not available, "
45 "falling back to CLOCK_REALTIME");
47 rb_warn("clock_gettime() totally broken, " \
48 "falling back to pure Ruby Clogger");
49 rb_raise(rb_eLoadError, "clock_gettime() broken");
52 static void clock_diff(struct timespec *a, const struct timespec *b)
54 a->tv_sec -= b->tv_sec;
55 a->tv_nsec -= b->tv_nsec;
56 if (a->tv_nsec < 0) {
57 --a->tv_sec;
58 a->tv_nsec += 1000000000;
62 /* give GCC hints for better branch prediction
63 * (we layout branches so that ASCII characters are handled faster) */
64 #if defined(__GNUC__) && (__GNUC__ >= 3)
65 # define likely(x) __builtin_expect (!!(x), 1)
66 # define unlikely(x) __builtin_expect (!!(x), 0)
67 #else
68 # define unlikely(x) (x)
69 # define likely(x) (x)
70 #endif
72 enum clogger_opcode {
73 CL_OP_LITERAL = 0,
74 CL_OP_REQUEST,
75 CL_OP_RESPONSE,
76 CL_OP_SPECIAL,
77 CL_OP_EVAL,
78 CL_OP_TIME_LOCAL,
79 CL_OP_TIME_UTC,
80 CL_OP_REQUEST_TIME,
81 CL_OP_TIME,
82 CL_OP_COOKIE
85 enum clogger_special {
86 CL_SP_body_bytes_sent = 0,
87 CL_SP_status,
88 CL_SP_request,
89 CL_SP_request_length,
90 CL_SP_response_length,
91 CL_SP_ip,
92 CL_SP_pid,
93 CL_SP_request_uri,
94 CL_SP_time_iso8601,
95 CL_SP_time_local,
96 CL_SP_time_utc
99 struct clogger {
100 VALUE app;
102 VALUE fmt_ops;
103 VALUE logger;
104 VALUE log_buf;
106 VALUE env;
107 VALUE cookies;
108 VALUE status;
109 VALUE headers;
110 VALUE body;
112 off_t body_bytes_sent;
113 struct timespec ts_start;
115 int fd;
116 int wrap_body;
117 int need_resp;
118 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
121 static ID ltlt_id;
122 static ID call_id;
123 static ID each_id;
124 static ID close_id;
125 static ID to_i_id;
126 static ID to_s_id;
127 static ID size_id;
128 static ID sq_brace_id;
129 static ID new_id;
130 static ID to_path_id;
131 static ID to_io_id;
132 static ID respond_to_id;
133 static VALUE cClogger;
134 static VALUE mFormat;
135 static VALUE cHeaderHash;
137 /* common hash lookup keys */
138 static VALUE g_HTTP_X_FORWARDED_FOR;
139 static VALUE g_REMOTE_ADDR;
140 static VALUE g_REQUEST_METHOD;
141 static VALUE g_PATH_INFO;
142 static VALUE g_REQUEST_URI;
143 static VALUE g_QUERY_STRING;
144 static VALUE g_HTTP_VERSION;
145 static VALUE g_rack_errors;
146 static VALUE g_rack_input;
147 static VALUE g_rack_multithread;
148 static VALUE g_dash;
149 static VALUE g_space;
150 static VALUE g_question_mark;
151 static VALUE g_rack_request_cookie_hash;
153 #define LOG_BUF_INIT_SIZE 128
155 static void init_buffers(struct clogger *c)
157 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
160 static inline int need_escape(unsigned c)
162 assert(c <= 0xff);
163 return !!(c == '\'' || c == '"' || c <= 0x1f);
166 /* we are encoding-agnostic, clients can send us all sorts of junk */
167 static VALUE byte_xs_str(VALUE from)
169 static const char esc[] = "0123456789ABCDEF";
170 unsigned char *new_ptr;
171 unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
172 long len = RSTRING_LEN(from);
173 long new_len = len;
174 VALUE rv;
176 for (; --len >= 0; ptr++) {
177 unsigned c = *ptr;
179 if (unlikely(need_escape(c)))
180 new_len += 3; /* { '\', 'x', 'X', 'X' } */
183 len = RSTRING_LEN(from);
184 if (new_len == len)
185 return from;
187 rv = rb_str_new(NULL, new_len);
188 new_ptr = (unsigned char *)RSTRING_PTR(rv);
189 ptr = (unsigned char *)RSTRING_PTR(from);
190 for (; --len >= 0; ptr++) {
191 unsigned c = *ptr;
193 if (unlikely(need_escape(c))) {
194 *new_ptr++ = '\\';
195 *new_ptr++ = 'x';
196 *new_ptr++ = esc[c >> 4];
197 *new_ptr++ = esc[c & 0xf];
198 } else {
199 *new_ptr++ = c;
202 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
204 return rv;
207 static VALUE byte_xs(VALUE from)
209 return byte_xs_str(rb_obj_as_string(from));
212 static void clogger_mark(void *ptr)
214 struct clogger *c = ptr;
216 rb_gc_mark(c->app);
217 rb_gc_mark(c->fmt_ops);
218 rb_gc_mark(c->logger);
219 rb_gc_mark(c->log_buf);
220 rb_gc_mark(c->env);
221 rb_gc_mark(c->cookies);
222 rb_gc_mark(c->status);
223 rb_gc_mark(c->headers);
224 rb_gc_mark(c->body);
227 static VALUE clogger_alloc(VALUE klass)
229 struct clogger *c;
231 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
234 static struct clogger *clogger_get(VALUE self)
236 struct clogger *c;
238 Data_Get_Struct(self, struct clogger, c);
239 assert(c);
240 return c;
243 /* only for writing to regular files, not stupid crap like NFS */
244 static void write_full(int fd, const void *buf, size_t count)
246 ssize_t r;
247 unsigned long ubuf = (unsigned long)buf;
249 while (count > 0) {
250 r = write(fd, (void *)ubuf, count);
252 if ((size_t)r == count) { /* overwhelmingly likely */
253 return;
254 } else if (r > 0) {
255 count -= r;
256 ubuf += r;
257 } else {
258 if (errno == EINTR || errno == EAGAIN)
259 continue; /* poor souls on NFS and like: */
260 if (!errno)
261 errno = ENOSPC;
262 rb_sys_fail("write");
268 * allow us to use write_full() iff we detect a blocking file
269 * descriptor that wouldn't play nicely with Ruby threading/fibers
271 static int raw_fd(VALUE my_fd)
273 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
274 int fd;
275 int flags;
277 if (NIL_P(my_fd))
278 return -1;
279 fd = NUM2INT(my_fd);
281 flags = fcntl(fd, F_GETFL);
282 if (flags < 0)
283 rb_sys_fail("fcntl");
285 if (flags & O_NONBLOCK) {
286 struct stat sb;
288 if (fstat(fd, &sb) < 0)
289 return -1;
291 /* O_NONBLOCK is no-op for regular files: */
292 if (! S_ISREG(sb.st_mode))
293 return -1;
295 return fd;
296 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
297 return -1;
298 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
301 /* :nodoc: */
302 static VALUE clogger_reentrant(VALUE self)
304 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
307 /* :nodoc: */
308 static VALUE clogger_wrap_body(VALUE self)
310 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
313 static void append_status(struct clogger *c)
315 char buf[sizeof("999")];
316 int nr;
317 VALUE status = c->status;
319 if (TYPE(status) != T_FIXNUM) {
320 status = rb_funcall(status, to_i_id, 0);
321 /* no way it's a valid status code (at least not HTTP/1.1) */
322 if (TYPE(status) != T_FIXNUM) {
323 rb_str_buf_append(c->log_buf, g_dash);
324 return;
328 nr = FIX2INT(status);
329 if (nr >= 100 && nr <= 999) {
330 nr = snprintf(buf, sizeof(buf), "%03d", nr);
331 assert(nr == 3);
332 rb_str_buf_cat(c->log_buf, buf, nr);
333 } else {
334 /* raise?, swap for 500? */
335 rb_str_buf_append(c->log_buf, g_dash);
339 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
340 static void append_ip(struct clogger *c)
342 VALUE env = c->env;
343 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
345 if (NIL_P(tmp)) {
346 /* can't be faked on any real server, so no escape */
347 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
348 if (NIL_P(tmp))
349 tmp = g_dash;
350 } else {
351 tmp = byte_xs(tmp);
353 rb_str_buf_append(c->log_buf, tmp);
356 static void append_body_bytes_sent(struct clogger *c)
358 char buf[(sizeof(off_t) * 8) / 3 + 1];
359 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
360 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
362 assert(nr > 0 && nr < (int)sizeof(buf));
363 rb_str_buf_cat(c->log_buf, buf, nr);
366 static void append_ts(struct clogger *c, const VALUE *op, struct timespec *ts)
368 char buf[sizeof(".000000") + ((sizeof(ts->tv_sec) * 8) / 3)];
369 int nr;
370 char *fmt = RSTRING_PTR(op[1]);
371 int ndiv = NUM2INT(op[2]);
372 int usec = ts->tv_nsec / 1000;
374 nr = snprintf(buf, sizeof(buf), fmt,
375 (int)ts->tv_sec, (int)(usec / ndiv));
376 assert(nr > 0 && nr < (int)sizeof(buf));
377 rb_str_buf_cat(c->log_buf, buf, nr);
380 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
382 struct timespec now;
384 clock_gettime(hopefully_CLOCK_MONOTONIC, &now);
385 clock_diff(&now, &c->ts_start);
386 append_ts(c, op, &now);
389 static void append_time_fmt(struct clogger *c, const VALUE *op)
391 struct timespec now;
392 int r = clock_gettime(CLOCK_REALTIME, &now);
394 if (unlikely(r != 0))
395 rb_sys_fail("clock_gettime(CLOCK_REALTIME)");
396 append_ts(c, op, &now);
399 static void append_request_uri(struct clogger *c)
401 VALUE tmp;
403 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
404 if (NIL_P(tmp)) {
405 tmp = rb_hash_aref(c->env, g_PATH_INFO);
406 if (!NIL_P(tmp))
407 rb_str_buf_append(c->log_buf, byte_xs(tmp));
408 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
409 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
410 rb_str_buf_append(c->log_buf, g_question_mark);
411 rb_str_buf_append(c->log_buf, byte_xs(tmp));
413 } else {
414 rb_str_buf_append(c->log_buf, byte_xs(tmp));
418 static void append_request(struct clogger *c)
420 VALUE tmp;
422 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
423 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
424 if (!NIL_P(tmp))
425 rb_str_buf_append(c->log_buf, tmp);
427 rb_str_buf_append(c->log_buf, g_space);
429 append_request_uri(c);
431 /* HTTP_VERSION can be injected by malicious clients */
432 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
433 if (!NIL_P(tmp)) {
434 rb_str_buf_append(c->log_buf, g_space);
435 rb_str_buf_append(c->log_buf, byte_xs(tmp));
439 static void append_request_length(struct clogger *c)
441 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
442 if (NIL_P(tmp)) {
443 rb_str_buf_append(c->log_buf, g_dash);
444 } else {
445 tmp = rb_funcall(tmp, size_id, 0);
446 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
450 static long local_gmtoffset(struct tm *tm)
452 time_t t = time(NULL);
454 tzset();
455 localtime_r(&t, tm);
456 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
457 return tm->tm_gmtoff / 60;
458 #else
459 return -(tm->tm_isdst ? timezone - 3600 : timezone) / 60;
460 #endif
463 static void append_time_iso8601(struct clogger *c)
465 char buf[sizeof("1970-01-01T00:00:00+00:00")];
466 struct tm tm;
467 int nr;
468 long gmtoff = local_gmtoffset(&tm);
470 nr = snprintf(buf, sizeof(buf),
471 "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
472 tm.tm_year + 1900, tm.tm_mon + 1,
473 tm.tm_mday, tm.tm_hour,
474 tm.tm_min, tm.tm_sec,
475 gmtoff < 0 ? '-' : '+',
476 abs(gmtoff / 60), abs(gmtoff % 60));
477 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
478 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
481 static const char months[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
482 "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
484 static void append_time_local(struct clogger *c)
486 char buf[sizeof("01/Jan/1970:00:00:00 +0000")];
487 struct tm tm;
488 int nr;
489 long gmtoff = local_gmtoffset(&tm);
491 nr = snprintf(buf, sizeof(buf),
492 "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
493 tm.tm_mday, months + (tm.tm_mon * sizeof("Jan")),
494 tm.tm_year + 1900, tm.tm_hour,
495 tm.tm_min, tm.tm_sec,
496 gmtoff < 0 ? '-' : '+',
497 abs(gmtoff / 60), abs(gmtoff % 60));
498 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
499 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
502 static void append_time_utc(struct clogger *c)
504 char buf[sizeof("01/Jan/1970:00:00:00 +0000")];
505 struct tm tm;
506 int nr;
507 time_t t = time(NULL);
509 gmtime_r(&t, &tm);
510 nr = snprintf(buf, sizeof(buf),
511 "%02d/%s/%d:%02d:%02d:%02d +0000",
512 tm.tm_mday, months + (tm.tm_mon * sizeof("Jan")),
513 tm.tm_year + 1900, tm.tm_hour,
514 tm.tm_min, tm.tm_sec);
515 assert(nr == (sizeof(buf) - 1) && "snprintf fail");
516 rb_str_buf_cat(c->log_buf, buf, sizeof(buf) - 1);
519 static void
520 append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt, VALUE buf)
522 char *buf_ptr = RSTRING_PTR(buf);
523 size_t buf_size = RSTRING_LEN(buf) + 1; /* "\0" */
524 size_t nr;
525 struct tm tmp;
526 time_t t = time(NULL);
528 if (op == CL_OP_TIME_LOCAL)
529 localtime_r(&t, &tmp);
530 else if (op == CL_OP_TIME_UTC)
531 gmtime_r(&t, &tmp);
532 else
533 assert(0 && "unknown op");
535 nr = strftime(buf_ptr, buf_size, RSTRING_PTR(fmt), &tmp);
536 assert(nr < buf_size && "time format too small!");
537 rb_str_buf_cat(c->log_buf, buf_ptr, nr);
540 static void append_pid(struct clogger *c)
542 char buf[(sizeof(pid_t) * 8) / 3 + 1];
543 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
545 assert(nr > 0 && nr < (int)sizeof(buf));
546 rb_str_buf_cat(c->log_buf, buf, nr);
549 static void append_eval(struct clogger *c, VALUE str)
551 int state = -1;
552 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
554 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
555 rb_str_buf_append(c->log_buf, rv);
558 static void append_cookie(struct clogger *c, VALUE key)
560 VALUE cookie;
562 if (c->cookies == Qfalse)
563 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
565 if (NIL_P(c->cookies)) {
566 cookie = g_dash;
567 } else {
568 cookie = rb_hash_aref(c->cookies, key);
569 if (NIL_P(cookie))
570 cookie = g_dash;
572 rb_str_buf_append(c->log_buf, cookie);
575 static void append_request_env(struct clogger *c, VALUE key)
577 VALUE tmp = rb_hash_aref(c->env, key);
579 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
580 rb_str_buf_append(c->log_buf, tmp);
583 static void append_response(struct clogger *c, VALUE key)
585 VALUE v;
587 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
589 v = rb_funcall(c->headers, sq_brace_id, 1, key);
590 v = NIL_P(v) ? g_dash : byte_xs(v);
591 rb_str_buf_append(c->log_buf, v);
594 static void special_var(struct clogger *c, enum clogger_special var)
596 switch (var) {
597 case CL_SP_body_bytes_sent:
598 append_body_bytes_sent(c);
599 break;
600 case CL_SP_status:
601 append_status(c);
602 break;
603 case CL_SP_request:
604 append_request(c);
605 break;
606 case CL_SP_request_length:
607 append_request_length(c);
608 break;
609 case CL_SP_response_length:
610 if (c->body_bytes_sent == 0)
611 rb_str_buf_append(c->log_buf, g_dash);
612 else
613 append_body_bytes_sent(c);
614 break;
615 case CL_SP_ip:
616 append_ip(c);
617 break;
618 case CL_SP_pid:
619 append_pid(c);
620 break;
621 case CL_SP_request_uri:
622 append_request_uri(c);
623 break;
624 case CL_SP_time_iso8601:
625 append_time_iso8601(c);
626 break;
627 case CL_SP_time_local:
628 append_time_local(c);
629 break;
630 case CL_SP_time_utc:
631 append_time_utc(c);
635 static VALUE cwrite(struct clogger *c)
637 const VALUE ops = c->fmt_ops;
638 const VALUE *ary = RARRAY_PTR(ops);
639 long i = RARRAY_LEN(ops);
640 VALUE dst = c->log_buf;
642 rb_str_set_len(dst, 0);
644 for (; --i >= 0; ary++) {
645 const VALUE *op = RARRAY_PTR(*ary);
646 enum clogger_opcode opcode = FIX2INT(op[0]);
648 switch (opcode) {
649 case CL_OP_LITERAL:
650 rb_str_buf_append(dst, op[1]);
651 break;
652 case CL_OP_REQUEST:
653 append_request_env(c, op[1]);
654 break;
655 case CL_OP_RESPONSE:
656 append_response(c, op[1]);
657 break;
658 case CL_OP_SPECIAL:
659 special_var(c, FIX2INT(op[1]));
660 break;
661 case CL_OP_EVAL:
662 append_eval(c, op[1]);
663 break;
664 case CL_OP_TIME_LOCAL:
665 case CL_OP_TIME_UTC:
666 append_time(c, opcode, op[1], op[2]);
667 break;
668 case CL_OP_REQUEST_TIME:
669 append_request_time_fmt(c, op);
670 break;
671 case CL_OP_TIME:
672 append_time_fmt(c, op);
673 break;
674 case CL_OP_COOKIE:
675 append_cookie(c, op[1]);
676 break;
680 if (c->fd >= 0) {
681 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
682 } else {
683 VALUE logger = c->logger;
685 if (NIL_P(logger))
686 logger = rb_hash_aref(c->env, g_rack_errors);
687 rb_funcall(logger, ltlt_id, 1, dst);
690 return Qnil;
693 static VALUE clogger_write(VALUE self)
695 return cwrite(clogger_get(self));
698 static void init_logger(struct clogger *c, VALUE path)
700 ID id;
702 if (!NIL_P(path) && !NIL_P(c->logger))
703 rb_raise(rb_eArgError, ":logger and :path are independent");
704 if (!NIL_P(path)) {
705 VALUE ab = rb_str_new2("ab");
706 id = rb_intern("open");
707 c->logger = rb_funcall(rb_cFile, id, 2, path, ab);
710 id = rb_intern("sync=");
711 if (rb_respond_to(c->logger, id))
712 rb_funcall(c->logger, id, 1, Qtrue);
714 id = rb_intern("fileno");
715 if (rb_respond_to(c->logger, id))
716 c->fd = raw_fd(rb_funcall(c->logger, id, 0));
720 * call-seq:
721 * Clogger.new(app, :logger => $stderr, :format => string) => obj
723 * Creates a new Clogger object that wraps +app+. +:logger+ may
724 * be any object that responds to the "<<" method with a string argument.
725 * Instead of +:logger+, +:path+ may be specified to be a :path of a File
726 * that will be opened in append mode.
728 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
730 struct clogger *c = clogger_get(self);
731 VALUE o = Qnil;
732 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
734 rb_scan_args(argc, argv, "11", &c->app, &o);
735 c->fd = -1;
736 c->logger = Qnil;
737 c->reentrant = -1; /* auto-detect */
739 if (TYPE(o) == T_HASH) {
740 VALUE tmp;
742 tmp = rb_hash_aref(o, ID2SYM(rb_intern("path")));
743 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
744 init_logger(c, tmp);
746 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
747 if (!NIL_P(tmp))
748 fmt = tmp;
750 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
751 switch (TYPE(tmp)) {
752 case T_TRUE:
753 c->reentrant = 1;
754 break;
755 case T_FALSE:
756 c->reentrant = 0;
757 case T_NIL:
758 break;
759 default:
760 rb_raise(rb_eArgError, ":reentrant must be boolean");
764 init_buffers(c);
765 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
767 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
768 1, c->fmt_ops))
769 c->need_resp = 1;
770 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
771 1, c->fmt_ops))
772 c->wrap_body = 1;
774 return self;
777 static VALUE body_iter_i(VALUE str, VALUE self)
779 struct clogger *c = clogger_get(self);
781 str = rb_obj_as_string(str);
782 c->body_bytes_sent += RSTRING_LEN(str);
784 return rb_yield(str);
787 static VALUE body_close(VALUE self)
789 struct clogger *c = clogger_get(self);
791 if (rb_respond_to(c->body, close_id))
792 return rb_funcall(c->body, close_id, 0);
793 return Qnil;
797 * call-seq:
798 * clogger.each { |part| socket.write(part) }
800 * Delegates the body#each call to the underlying +body+ object
801 * while tracking the number of bytes yielded. This will log
802 * the request.
804 static VALUE clogger_each(VALUE self)
806 struct clogger *c = clogger_get(self);
808 rb_need_block();
809 c->body_bytes_sent = 0;
810 rb_iterate(rb_each, c->body, body_iter_i, self);
812 return self;
816 * call-seq:
817 * clogger.close
819 * Delegates the body#close call to the underlying +body+ object.
820 * This is only used when Clogger is wrapping the +body+ of a Rack
821 * response and should be automatically called by the web server.
823 static VALUE clogger_close(VALUE self)
826 return rb_ensure(body_close, self, clogger_write, self);
829 /* :nodoc: */
830 static VALUE clogger_fileno(VALUE self)
832 struct clogger *c = clogger_get(self);
834 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
837 static VALUE ccall(struct clogger *c, VALUE env)
839 VALUE rv;
841 clock_gettime(hopefully_CLOCK_MONOTONIC, &c->ts_start);
842 c->env = env;
843 c->cookies = Qfalse;
844 rv = rb_funcall(c->app, call_id, 1, env);
845 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
846 VALUE *tmp = RARRAY_PTR(rv);
848 c->status = tmp[0];
849 c->headers = tmp[1];
850 c->body = tmp[2];
852 rv = rb_ary_new4(3, tmp);
853 if (c->need_resp &&
854 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
855 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
856 rb_ary_store(rv, 1, c->headers);
858 } else {
859 volatile VALUE tmp = rb_inspect(rv);
861 c->status = INT2FIX(500);
862 c->headers = c->body = rb_ary_new();
863 cwrite(c);
864 rb_raise(rb_eTypeError,
865 "app response not a 3 element Array: %s",
866 RSTRING_PTR(tmp));
869 return rv;
873 * call-seq:
874 * clogger.call(env) => [ status, headers, body ]
876 * calls the wrapped Rack application with +env+, returns the
877 * [status, headers, body ] tuplet required by Rack.
879 static VALUE clogger_call(VALUE self, VALUE env)
881 struct clogger *c = clogger_get(self);
882 VALUE rv;
884 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
886 if (c->wrap_body) {
887 if (c->reentrant < 0) {
888 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
889 c->reentrant = Qfalse == tmp ? 0 : 1;
891 if (c->reentrant) {
892 self = rb_obj_dup(self);
893 c = clogger_get(self);
896 rv = ccall(c, env);
897 assert(!OBJ_FROZEN(rv) && "frozen response array");
898 rb_ary_store(rv, 2, self);
900 return rv;
903 rv = ccall(c, env);
904 cwrite(c);
906 return rv;
909 static void duplicate_buffers(VALUE ops)
911 long i = RARRAY_LEN(ops);
912 VALUE *ary = RARRAY_PTR(ops);
914 for ( ; --i >= 0; ary++) {
915 VALUE *op = RARRAY_PTR(*ary);
916 enum clogger_opcode opcode = FIX2INT(op[0]);
918 if (opcode == CL_OP_TIME_LOCAL || opcode == CL_OP_TIME_UTC) {
919 Check_Type(op[2], T_STRING);
920 op[2] = rb_str_dup(op[2]);
921 rb_str_modify(op[2]); /* trigger copy-on-write */
926 /* :nodoc: */
927 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
929 struct clogger *a = clogger_get(orig);
930 struct clogger *b = clogger_get(clone);
932 memcpy(b, a, sizeof(struct clogger));
933 init_buffers(b);
934 duplicate_buffers(b->fmt_ops);
936 return clone;
939 #define CONST_GLOBAL_STR2(var, val) do { \
940 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
941 rb_global_variable(&g_##var); \
942 } while (0)
944 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
947 * call-seq:
948 * clogger.respond_to?(:to_path) => true or false
949 * clogger.respond_to?(:close) => true
951 * used to delegate +:to_path+ checks for Rack webservers that optimize
952 * static file serving
954 static VALUE respond_to(VALUE self, VALUE method)
956 struct clogger *c = clogger_get(self);
957 ID id = rb_to_id(method);
959 if (close_id == id)
960 return Qtrue;
961 return rb_respond_to(c->body, id);
965 * call-seq:
966 * clogger.to_path
968 * used to proxy +:to_path+ method calls to the wrapped response body.
970 static VALUE to_path(VALUE self)
972 struct clogger *c = clogger_get(self);
973 struct stat sb;
974 int rv;
975 VALUE path = rb_funcall(c->body, to_path_id, 0);
977 /* try to avoid an extra path lookup */
978 if (rb_respond_to(c->body, to_io_id)) {
979 rv = fstat(my_fileno(c->body), &sb);
980 } else {
981 const char *cpath = StringValueCStr(path);
982 unsigned devfd;
984 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
985 * extra open() syscalls, too.
987 if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
988 rv = fstat((int)devfd, &sb);
989 else
990 rv = stat(cpath, &sb);
994 * calling this method implies the web server will bypass
995 * the each method where body_bytes_sent is calculated,
996 * so we stat and set that value here.
998 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
999 return path;
1003 * call-seq:
1004 * clogger.to_io
1006 * used to proxy +:to_io+ method calls to the wrapped response body.
1008 static VALUE to_io(VALUE self)
1010 struct clogger *c = clogger_get(self);
1011 struct stat sb;
1012 VALUE io = rb_convert_type(c->body, T_FILE, "IO", "to_io");
1014 if (fstat(my_fileno(io), &sb) == 0)
1015 c->body_bytes_sent = sb.st_size;
1017 return io;
1020 /* :nodoc: */
1021 static VALUE body(VALUE self)
1023 return clogger_get(self)->body;
1026 void Init_clogger_ext(void)
1028 VALUE tmp;
1030 check_clock();
1032 ltlt_id = rb_intern("<<");
1033 call_id = rb_intern("call");
1034 each_id = rb_intern("each");
1035 close_id = rb_intern("close");
1036 to_i_id = rb_intern("to_i");
1037 to_s_id = rb_intern("to_s");
1038 size_id = rb_intern("size");
1039 sq_brace_id = rb_intern("[]");
1040 new_id = rb_intern("new");
1041 to_path_id = rb_intern("to_path");
1042 to_io_id = rb_intern("to_io");
1043 respond_to_id = rb_intern("respond_to?");
1044 cClogger = rb_define_class("Clogger", rb_cObject);
1045 mFormat = rb_define_module_under(cClogger, "Format");
1046 rb_define_alloc_func(cClogger, clogger_alloc);
1047 rb_define_method(cClogger, "initialize", clogger_init, -1);
1048 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
1049 rb_define_method(cClogger, "call", clogger_call, 1);
1050 rb_define_method(cClogger, "each", clogger_each, 0);
1051 rb_define_method(cClogger, "close", clogger_close, 0);
1052 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
1053 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
1054 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
1055 rb_define_method(cClogger, "to_path", to_path, 0);
1056 rb_define_method(cClogger, "to_io", to_io, 0);
1057 rb_define_method(cClogger, "respond_to?", respond_to, 1);
1058 rb_define_method(cClogger, "body", body, 0);
1059 CONST_GLOBAL_STR(REMOTE_ADDR);
1060 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
1061 CONST_GLOBAL_STR(REQUEST_METHOD);
1062 CONST_GLOBAL_STR(PATH_INFO);
1063 CONST_GLOBAL_STR(QUERY_STRING);
1064 CONST_GLOBAL_STR(REQUEST_URI);
1065 CONST_GLOBAL_STR(HTTP_VERSION);
1066 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
1067 CONST_GLOBAL_STR2(rack_input, "rack.input");
1068 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
1069 CONST_GLOBAL_STR2(dash, "-");
1070 CONST_GLOBAL_STR2(space, " ");
1071 CONST_GLOBAL_STR2(question_mark, "?");
1072 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
1074 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
1075 tmp = rb_const_get(tmp, rb_intern("Utils"));
1076 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));