accept a new :path argument in initialize
[clogger.git] / ext / clogger_ext / clogger.c
blob09eeffd12dc7f07cadb3a3773a9f918256cbf109
1 #define _BSD_SOURCE
2 #include <ruby.h>
3 #ifdef HAVE_RUBY_IO_H
4 # include <ruby/io.h>
5 #else
6 # include <rubyio.h>
7 #endif
8 #include <assert.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13 #include <time.h>
14 #include <errno.h>
15 #ifdef HAVE_FCNTL_H
16 # include <fcntl.h>
17 #endif
18 #include "ruby_1_9_compat.h"
20 /* in case _BSD_SOURCE doesn't give us this macro */
21 #ifndef timersub
22 # define timersub(a, b, result) \
23 do { \
24 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
25 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
26 if ((result)->tv_usec < 0) { \
27 --(result)->tv_sec; \
28 (result)->tv_usec += 1000000; \
29 } \
30 } while (0)
31 #endif
33 /* give GCC hints for better branch prediction
34 * (we layout branches so that ASCII characters are handled faster) */
35 #if defined(__GNUC__) && (__GNUC__ >= 3)
36 # define likely(x) __builtin_expect (!!(x), 1)
37 # define unlikely(x) __builtin_expect (!!(x), 0)
38 #else
39 # define unlikely(x) (x)
40 # define likely(x) (x)
41 #endif
43 enum clogger_opcode {
44 CL_OP_LITERAL = 0,
45 CL_OP_REQUEST,
46 CL_OP_RESPONSE,
47 CL_OP_SPECIAL,
48 CL_OP_EVAL,
49 CL_OP_TIME_LOCAL,
50 CL_OP_TIME_UTC,
51 CL_OP_REQUEST_TIME,
52 CL_OP_TIME,
53 CL_OP_COOKIE
56 enum clogger_special {
57 CL_SP_body_bytes_sent = 0,
58 CL_SP_status,
59 CL_SP_request,
60 CL_SP_request_length,
61 CL_SP_response_length,
62 CL_SP_ip,
63 CL_SP_pid,
64 CL_SP_request_uri
67 struct clogger {
68 VALUE app;
70 VALUE fmt_ops;
71 VALUE logger;
72 VALUE log_buf;
74 VALUE env;
75 VALUE cookies;
76 VALUE status;
77 VALUE headers;
78 VALUE body;
80 off_t body_bytes_sent;
81 struct timeval tv_start;
83 int fd;
84 int wrap_body;
85 int need_resp;
86 int reentrant; /* tri-state, -1:auto, 1/0 true/false */
89 static ID ltlt_id;
90 static ID call_id;
91 static ID each_id;
92 static ID close_id;
93 static ID to_i_id;
94 static ID to_s_id;
95 static ID size_id;
96 static ID sq_brace_id;
97 static ID new_id;
98 static ID to_path_id;
99 static ID to_io_id;
100 static VALUE cClogger;
101 static VALUE cToPath;
102 static VALUE mFormat;
103 static VALUE cHeaderHash;
105 /* common hash lookup keys */
106 static VALUE g_HTTP_X_FORWARDED_FOR;
107 static VALUE g_REMOTE_ADDR;
108 static VALUE g_REQUEST_METHOD;
109 static VALUE g_PATH_INFO;
110 static VALUE g_REQUEST_URI;
111 static VALUE g_QUERY_STRING;
112 static VALUE g_HTTP_VERSION;
113 static VALUE g_rack_errors;
114 static VALUE g_rack_input;
115 static VALUE g_rack_multithread;
116 static VALUE g_dash;
117 static VALUE g_space;
118 static VALUE g_question_mark;
119 static VALUE g_rack_request_cookie_hash;
121 #define LOG_BUF_INIT_SIZE 128
123 static void init_buffers(struct clogger *c)
125 c->log_buf = rb_str_buf_new(LOG_BUF_INIT_SIZE);
128 static inline int need_escape(unsigned c)
130 assert(c <= 0xff);
131 return !!(c == '\'' || c == '"' || c <= 0x1f);
134 /* we are encoding-agnostic, clients can send us all sorts of junk */
135 static VALUE byte_xs_str(VALUE from)
137 static const char esc[] = "0123456789ABCDEF";
138 unsigned char *new_ptr;
139 unsigned char *ptr = (unsigned char *)RSTRING_PTR(from);
140 long len = RSTRING_LEN(from);
141 long new_len = len;
142 VALUE rv;
144 for (; --len >= 0; ptr++) {
145 unsigned c = *ptr;
147 if (unlikely(need_escape(c)))
148 new_len += 3; /* { '\', 'x', 'X', 'X' } */
151 len = RSTRING_LEN(from);
152 if (new_len == len)
153 return from;
155 rv = rb_str_new(NULL, new_len);
156 new_ptr = (unsigned char *)RSTRING_PTR(rv);
157 ptr = (unsigned char *)RSTRING_PTR(from);
158 for (; --len >= 0; ptr++) {
159 unsigned c = *ptr;
161 if (unlikely(need_escape(c))) {
162 *new_ptr++ = '\\';
163 *new_ptr++ = 'x';
164 *new_ptr++ = esc[c >> 4];
165 *new_ptr++ = esc[c & 0xf];
166 } else {
167 *new_ptr++ = c;
170 assert(RSTRING_PTR(rv)[RSTRING_LEN(rv)] == '\0');
172 return rv;
175 static VALUE byte_xs(VALUE from)
177 return byte_xs_str(rb_obj_as_string(from));
180 static void clogger_mark(void *ptr)
182 struct clogger *c = ptr;
184 rb_gc_mark(c->app);
185 rb_gc_mark(c->fmt_ops);
186 rb_gc_mark(c->logger);
187 rb_gc_mark(c->log_buf);
188 rb_gc_mark(c->env);
189 rb_gc_mark(c->cookies);
190 rb_gc_mark(c->status);
191 rb_gc_mark(c->headers);
192 rb_gc_mark(c->body);
195 static VALUE clogger_alloc(VALUE klass)
197 struct clogger *c;
199 return Data_Make_Struct(klass, struct clogger, clogger_mark, -1, c);
202 static struct clogger *clogger_get(VALUE self)
204 struct clogger *c;
206 Data_Get_Struct(self, struct clogger, c);
207 assert(c);
208 return c;
211 static VALUE obj_fileno(VALUE obj)
213 return rb_funcall(obj, rb_intern("fileno"), 0);
216 /* only for writing to regular files, not stupid crap like NFS */
217 static void write_full(int fd, const void *buf, size_t count)
219 ssize_t r;
220 unsigned long ubuf = (unsigned long)buf;
222 while (count > 0) {
223 r = write(fd, (void *)ubuf, count);
225 if ((size_t)r == count) { /* overwhelmingly likely */
226 return;
227 } else if (r > 0) {
228 count -= r;
229 ubuf += r;
230 } else {
231 if (errno == EINTR || errno == EAGAIN)
232 continue; /* poor souls on NFS and like: */
233 if (!errno)
234 errno = ENOSPC;
235 rb_sys_fail("write");
241 * allow us to use write_full() iff we detect a blocking file
242 * descriptor that wouldn't play nicely with Ruby threading/fibers
244 static int raw_fd(VALUE my_fd)
246 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
247 int fd;
248 int flags;
250 if (NIL_P(my_fd))
251 return -1;
252 fd = NUM2INT(my_fd);
254 flags = fcntl(fd, F_GETFL);
255 if (flags < 0)
256 rb_sys_fail("fcntl");
258 if (flags & O_NONBLOCK) {
259 struct stat sb;
261 if (fstat(fd, &sb) < 0)
262 return -1;
264 /* O_NONBLOCK is no-op for regular files: */
265 if (! S_ISREG(sb.st_mode))
266 return -1;
268 return fd;
269 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
270 return -1;
271 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
274 /* :nodoc: */
275 static VALUE clogger_reentrant(VALUE self)
277 return clogger_get(self)->reentrant == 0 ? Qfalse : Qtrue;
280 /* :nodoc: */
281 static VALUE clogger_wrap_body(VALUE self)
283 return clogger_get(self)->wrap_body == 0 ? Qfalse : Qtrue;
286 static void append_status(struct clogger *c)
288 char buf[sizeof("999")];
289 int nr;
290 VALUE status = c->status;
292 if (TYPE(status) != T_FIXNUM) {
293 status = rb_funcall(status, to_i_id, 0);
294 /* no way it's a valid status code (at least not HTTP/1.1) */
295 if (TYPE(status) != T_FIXNUM) {
296 rb_str_buf_append(c->log_buf, g_dash);
297 return;
301 nr = FIX2INT(status);
302 if (nr >= 100 && nr <= 999) {
303 nr = snprintf(buf, sizeof(buf), "%03d", nr);
304 assert(nr == 3);
305 rb_str_buf_cat(c->log_buf, buf, nr);
306 } else {
307 /* raise?, swap for 500? */
308 rb_str_buf_append(c->log_buf, g_dash);
312 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
313 static void append_ip(struct clogger *c)
315 VALUE env = c->env;
316 VALUE tmp = rb_hash_aref(env, g_HTTP_X_FORWARDED_FOR);
318 if (NIL_P(tmp)) {
319 /* can't be faked on any real server, so no escape */
320 tmp = rb_hash_aref(env, g_REMOTE_ADDR);
321 if (NIL_P(tmp))
322 tmp = g_dash;
323 } else {
324 tmp = byte_xs(tmp);
326 rb_str_buf_append(c->log_buf, tmp);
329 static void append_body_bytes_sent(struct clogger *c)
331 char buf[(sizeof(off_t) * 8) / 3 + 1];
332 const char *fmt = sizeof(off_t) == sizeof(long) ? "%ld" : "%lld";
333 int nr = snprintf(buf, sizeof(buf), fmt, c->body_bytes_sent);
335 assert(nr > 0 && nr < (int)sizeof(buf));
336 rb_str_buf_cat(c->log_buf, buf, nr);
339 static void append_tv(struct clogger *c, const VALUE *op, struct timeval *tv)
341 char buf[sizeof(".000000") + ((sizeof(tv->tv_sec) * 8) / 3)];
342 int nr;
343 char *fmt = RSTRING_PTR(op[1]);
344 int ndiv = NUM2INT(op[2]);
346 nr = snprintf(buf, sizeof(buf), fmt,
347 (int)tv->tv_sec, (int)(tv->tv_usec / ndiv));
348 assert(nr > 0 && nr < (int)sizeof(buf));
349 rb_str_buf_cat(c->log_buf, buf, nr);
352 static void append_request_time_fmt(struct clogger *c, const VALUE *op)
354 struct timeval now, d;
356 gettimeofday(&now, NULL);
357 timersub(&now, &c->tv_start, &d);
358 append_tv(c, op, &d);
361 static void append_time_fmt(struct clogger *c, const VALUE *op)
363 struct timeval now;
365 gettimeofday(&now, NULL);
366 append_tv(c, op, &now);
369 static void append_request_uri(struct clogger *c)
371 VALUE tmp;
373 tmp = rb_hash_aref(c->env, g_REQUEST_URI);
374 if (NIL_P(tmp)) {
375 tmp = rb_hash_aref(c->env, g_PATH_INFO);
376 if (!NIL_P(tmp))
377 rb_str_buf_append(c->log_buf, byte_xs(tmp));
378 tmp = rb_hash_aref(c->env, g_QUERY_STRING);
379 if (!NIL_P(tmp) && RSTRING_LEN(tmp) != 0) {
380 rb_str_buf_append(c->log_buf, g_question_mark);
381 rb_str_buf_append(c->log_buf, byte_xs(tmp));
383 } else {
384 rb_str_buf_append(c->log_buf, byte_xs(tmp));
388 static void append_request(struct clogger *c)
390 VALUE tmp;
392 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
393 tmp = rb_hash_aref(c->env, g_REQUEST_METHOD);
394 if (!NIL_P(tmp))
395 rb_str_buf_append(c->log_buf, tmp);
397 rb_str_buf_append(c->log_buf, g_space);
399 append_request_uri(c);
401 /* HTTP_VERSION can be injected by malicious clients */
402 tmp = rb_hash_aref(c->env, g_HTTP_VERSION);
403 if (!NIL_P(tmp)) {
404 rb_str_buf_append(c->log_buf, g_space);
405 rb_str_buf_append(c->log_buf, byte_xs(tmp));
409 static void append_request_length(struct clogger *c)
411 VALUE tmp = rb_hash_aref(c->env, g_rack_input);
412 if (NIL_P(tmp)) {
413 rb_str_buf_append(c->log_buf, g_dash);
414 } else {
415 tmp = rb_funcall(tmp, size_id, 0);
416 rb_str_buf_append(c->log_buf, rb_funcall(tmp, to_s_id, 0));
420 static void append_time(struct clogger *c, enum clogger_opcode op, VALUE fmt)
422 /* you'd have to be a moron to use formats this big... */
423 char buf[sizeof("Saturday, November 01, 1970, 00:00:00 PM +0000")];
424 size_t nr;
425 struct tm tmp;
426 time_t t = time(NULL);
428 if (op == CL_OP_TIME_LOCAL)
429 localtime_r(&t, &tmp);
430 else if (op == CL_OP_TIME_UTC)
431 gmtime_r(&t, &tmp);
432 else
433 assert(0 && "unknown op");
435 nr = strftime(buf, sizeof(buf), RSTRING_PTR(fmt), &tmp);
436 if (nr == 0 || nr == sizeof(buf))
437 rb_str_buf_append(c->log_buf, g_dash);
438 else
439 rb_str_buf_cat(c->log_buf, buf, nr);
442 static void append_pid(struct clogger *c)
444 char buf[(sizeof(pid_t) * 8) / 3 + 1];
445 int nr = snprintf(buf, sizeof(buf), "%d", (int)getpid());
447 assert(nr > 0 && nr < (int)sizeof(buf));
448 rb_str_buf_cat(c->log_buf, buf, nr);
451 static void append_eval(struct clogger *c, VALUE str)
453 int state = -1;
454 VALUE rv = rb_eval_string_protect(RSTRING_PTR(str), &state);
456 rv = state == 0 ? rb_obj_as_string(rv) : g_dash;
457 rb_str_buf_append(c->log_buf, rv);
460 static void append_cookie(struct clogger *c, VALUE key)
462 VALUE cookie;
464 if (c->cookies == Qfalse)
465 c->cookies = rb_hash_aref(c->env, g_rack_request_cookie_hash);
467 if (NIL_P(c->cookies)) {
468 cookie = g_dash;
469 } else {
470 cookie = rb_hash_aref(c->cookies, key);
471 if (NIL_P(cookie))
472 cookie = g_dash;
474 rb_str_buf_append(c->log_buf, cookie);
477 static void append_request_env(struct clogger *c, VALUE key)
479 VALUE tmp = rb_hash_aref(c->env, key);
481 tmp = NIL_P(tmp) ? g_dash : byte_xs(tmp);
482 rb_str_buf_append(c->log_buf, tmp);
485 static void append_response(struct clogger *c, VALUE key)
487 VALUE v;
489 assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash");
491 v = rb_funcall(c->headers, sq_brace_id, 1, key);
492 v = NIL_P(v) ? g_dash : byte_xs(v);
493 rb_str_buf_append(c->log_buf, v);
496 static void special_var(struct clogger *c, enum clogger_special var)
498 switch (var) {
499 case CL_SP_body_bytes_sent:
500 append_body_bytes_sent(c);
501 break;
502 case CL_SP_status:
503 append_status(c);
504 break;
505 case CL_SP_request:
506 append_request(c);
507 break;
508 case CL_SP_request_length:
509 append_request_length(c);
510 break;
511 case CL_SP_response_length:
512 if (c->body_bytes_sent == 0)
513 rb_str_buf_append(c->log_buf, g_dash);
514 else
515 append_body_bytes_sent(c);
516 break;
517 case CL_SP_ip:
518 append_ip(c);
519 break;
520 case CL_SP_pid:
521 append_pid(c);
522 break;
523 case CL_SP_request_uri:
524 append_request_uri(c);
528 static VALUE cwrite(struct clogger *c)
530 const VALUE ops = c->fmt_ops;
531 const VALUE *ary = RARRAY_PTR(ops);
532 long i = RARRAY_LEN(ops);
533 VALUE dst = c->log_buf;
535 rb_str_set_len(dst, 0);
537 for (; --i >= 0; ary++) {
538 const VALUE *op = RARRAY_PTR(*ary);
539 enum clogger_opcode opcode = FIX2INT(op[0]);
541 switch (opcode) {
542 case CL_OP_LITERAL:
543 rb_str_buf_append(dst, op[1]);
544 break;
545 case CL_OP_REQUEST:
546 append_request_env(c, op[1]);
547 break;
548 case CL_OP_RESPONSE:
549 append_response(c, op[1]);
550 break;
551 case CL_OP_SPECIAL:
552 special_var(c, FIX2INT(op[1]));
553 break;
554 case CL_OP_EVAL:
555 append_eval(c, op[1]);
556 break;
557 case CL_OP_TIME_LOCAL:
558 case CL_OP_TIME_UTC:
559 append_time(c, opcode, op[1]);
560 break;
561 case CL_OP_REQUEST_TIME:
562 append_request_time_fmt(c, op);
563 break;
564 case CL_OP_TIME:
565 append_time_fmt(c, op);
566 break;
567 case CL_OP_COOKIE:
568 append_cookie(c, op[1]);
569 break;
573 if (c->fd >= 0) {
574 write_full(c->fd, RSTRING_PTR(dst), RSTRING_LEN(dst));
575 } else {
576 VALUE logger = c->logger;
578 if (NIL_P(logger))
579 logger = rb_hash_aref(c->env, g_rack_errors);
580 rb_funcall(logger, ltlt_id, 1, dst);
583 return Qnil;
586 static void init_logger(struct clogger *c, VALUE path)
588 ID id;
590 if (!NIL_P(path) && !NIL_P(c->logger))
591 rb_raise(rb_eArgError, ":logger and :path are independent");
592 if (!NIL_P(path)) {
593 VALUE ab = rb_str_new2("ab");
594 id = rb_intern("open");
595 c->logger = rb_funcall(rb_cFile, id, 2, path, ab);
598 id = rb_intern("sync=");
599 if (rb_respond_to(c->logger, id))
600 rb_funcall(c->logger, id, 1, Qtrue);
602 id = rb_intern("fileno");
603 if (rb_respond_to(c->logger, id))
604 c->fd = raw_fd(rb_funcall(c->logger, id, 0));
608 * call-seq:
609 * Clogger.new(app, :logger => $stderr, :format => string) => obj
611 * Creates a new Clogger object that wraps +app+. +:logger+ may
612 * be any object that responds to the "<<" method with a string argument.
613 * If +:logger:+ is a string, it will be treated as a path to a
614 * File that will be opened in append mode.
616 static VALUE clogger_init(int argc, VALUE *argv, VALUE self)
618 struct clogger *c = clogger_get(self);
619 VALUE o = Qnil;
620 VALUE fmt = rb_const_get(mFormat, rb_intern("Common"));
622 rb_scan_args(argc, argv, "11", &c->app, &o);
623 c->fd = -1;
624 c->logger = Qnil;
625 c->reentrant = -1; /* auto-detect */
627 if (TYPE(o) == T_HASH) {
628 VALUE tmp;
630 tmp = rb_hash_aref(o, ID2SYM(rb_intern("path")));
631 c->logger = rb_hash_aref(o, ID2SYM(rb_intern("logger")));
632 init_logger(c, tmp);
634 tmp = rb_hash_aref(o, ID2SYM(rb_intern("format")));
635 if (!NIL_P(tmp))
636 fmt = tmp;
638 tmp = rb_hash_aref(o, ID2SYM(rb_intern("reentrant")));
639 switch (TYPE(tmp)) {
640 case T_TRUE:
641 c->reentrant = 1;
642 break;
643 case T_FALSE:
644 c->reentrant = 0;
645 case T_NIL:
646 break;
647 default:
648 rb_raise(rb_eArgError, ":reentrant must be boolean");
652 init_buffers(c);
653 c->fmt_ops = rb_funcall(self, rb_intern("compile_format"), 2, fmt, o);
655 if (Qtrue == rb_funcall(self, rb_intern("need_response_headers?"),
656 1, c->fmt_ops))
657 c->need_resp = 1;
658 if (Qtrue == rb_funcall(self, rb_intern("need_wrap_body?"),
659 1, c->fmt_ops))
660 c->wrap_body = 1;
662 return self;
665 static VALUE body_iter_i(VALUE str, VALUE memop)
667 off_t *len = (off_t *)memop;
669 str = rb_obj_as_string(str);
670 *len += RSTRING_LEN(str);
672 return rb_yield(str);
675 static VALUE body_close(struct clogger *c)
677 if (rb_respond_to(c->body, close_id))
678 return rb_funcall(c->body, close_id, 0);
679 return Qnil;
683 * call-seq:
684 * clogger.each { |part| socket.write(part) }
686 * Delegates the body#each call to the underlying +body+ object
687 * while tracking the number of bytes yielded. This will log
688 * the request.
690 static VALUE clogger_each(VALUE self)
692 struct clogger *c = clogger_get(self);
694 rb_need_block();
695 c->body_bytes_sent = 0;
696 rb_iterate(rb_each, c->body, body_iter_i, (VALUE)&c->body_bytes_sent);
698 return self;
702 * call-seq:
703 * clogger.close
705 * Delegates the body#close call to the underlying +body+ object.
706 * This is only used when Clogger is wrapping the +body+ of a Rack
707 * response and should be automatically called by the web server.
709 static VALUE clogger_close(VALUE self)
711 struct clogger *c = clogger_get(self);
713 return rb_ensure(body_close, (VALUE)c, cwrite, (VALUE)c);
716 /* :nodoc: */
717 static VALUE clogger_fileno(VALUE self)
719 struct clogger *c = clogger_get(self);
721 return c->fd < 0 ? Qnil : INT2NUM(c->fd);
724 static VALUE ccall(struct clogger *c, VALUE env)
726 VALUE rv;
728 gettimeofday(&c->tv_start, NULL);
729 c->env = env;
730 c->cookies = Qfalse;
731 rv = rb_funcall(c->app, call_id, 1, env);
732 if (TYPE(rv) == T_ARRAY && RARRAY_LEN(rv) == 3) {
733 VALUE *tmp = RARRAY_PTR(rv);
735 c->status = tmp[0];
736 c->headers = tmp[1];
737 c->body = tmp[2];
739 rv = rb_ary_new4(3, tmp);
740 if (c->need_resp &&
741 ! rb_obj_is_kind_of(tmp[1], cHeaderHash)) {
742 c->headers = rb_funcall(cHeaderHash, new_id, 1, tmp[1]);
743 rb_ary_store(rv, 1, c->headers);
745 } else {
746 volatile VALUE tmp = rb_inspect(rv);
748 c->status = INT2FIX(500);
749 c->headers = c->body = rb_ary_new();
750 cwrite(c);
751 rb_raise(rb_eTypeError,
752 "app response not a 3 element Array: %s",
753 RSTRING_PTR(tmp));
756 return rv;
760 * call-seq:
761 * clogger.call(env) => [ status, headers, body ]
763 * calls the wrapped Rack application with +env+, returns the
764 * [status, headers, body ] tuplet required by Rack.
766 static VALUE clogger_call(VALUE self, VALUE env)
768 struct clogger *c = clogger_get(self);
769 VALUE rv;
771 env = rb_check_convert_type(env, T_HASH, "Hash", "to_hash");
773 if (c->wrap_body) {
774 if (c->reentrant < 0) {
775 VALUE tmp = rb_hash_aref(env, g_rack_multithread);
776 c->reentrant = Qfalse == tmp ? 0 : 1;
778 if (c->reentrant) {
779 self = rb_obj_dup(self);
780 c = clogger_get(self);
783 rv = ccall(c, env);
784 assert(!OBJ_FROZEN(rv) && "frozen response array");
786 if (rb_respond_to(c->body, to_path_id))
787 self = rb_funcall(cToPath, new_id, 1, self);
788 rb_ary_store(rv, 2, self);
790 return rv;
793 rv = ccall(c, env);
794 cwrite(c);
796 return rv;
799 /* :nodoc */
800 static VALUE clogger_init_copy(VALUE clone, VALUE orig)
802 struct clogger *a = clogger_get(orig);
803 struct clogger *b = clogger_get(clone);
805 memcpy(b, a, sizeof(struct clogger));
806 init_buffers(b);
808 return clone;
811 #define CONST_GLOBAL_STR2(var, val) do { \
812 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
813 rb_global_variable(&g_##var); \
814 } while (0)
816 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
818 static VALUE to_path(VALUE self)
820 struct clogger *c = clogger_get(RSTRUCT_PTR(self)[0]);
821 VALUE path = rb_funcall(c->body, to_path_id, 0);
822 struct stat sb;
823 int rv;
824 unsigned devfd;
825 const char *cpath;
827 Check_Type(path, T_STRING);
828 cpath = RSTRING_PTR(path);
830 /* try to avoid an extra path lookup */
831 if (rb_respond_to(c->body, to_io_id))
832 rv = fstat(my_fileno(c->body), &sb);
834 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
835 * extra open() syscalls, too.
837 else if (sscanf(cpath, "/dev/fd/%u", &devfd) == 1)
838 rv = fstat((int)devfd, &sb);
839 else
840 rv = stat(cpath, &sb);
843 * calling this method implies the web server will bypass
844 * the each method where body_bytes_sent is calculated,
845 * so we stat and set that value here.
847 c->body_bytes_sent = rv == 0 ? sb.st_size : 0;
848 return path;
851 void Init_clogger_ext(void)
853 VALUE tmp;
855 ltlt_id = rb_intern("<<");
856 call_id = rb_intern("call");
857 each_id = rb_intern("each");
858 close_id = rb_intern("close");
859 to_i_id = rb_intern("to_i");
860 to_s_id = rb_intern("to_s");
861 size_id = rb_intern("size");
862 sq_brace_id = rb_intern("[]");
863 new_id = rb_intern("new");
864 to_path_id = rb_intern("to_path");
865 to_io_id = rb_intern("to_io");
866 cClogger = rb_define_class("Clogger", rb_cObject);
867 mFormat = rb_define_module_under(cClogger, "Format");
868 rb_define_alloc_func(cClogger, clogger_alloc);
869 rb_define_method(cClogger, "initialize", clogger_init, -1);
870 rb_define_method(cClogger, "initialize_copy", clogger_init_copy, 1);
871 rb_define_method(cClogger, "call", clogger_call, 1);
872 rb_define_method(cClogger, "each", clogger_each, 0);
873 rb_define_method(cClogger, "close", clogger_close, 0);
874 rb_define_method(cClogger, "fileno", clogger_fileno, 0);
875 rb_define_method(cClogger, "wrap_body?", clogger_wrap_body, 0);
876 rb_define_method(cClogger, "reentrant?", clogger_reentrant, 0);
877 CONST_GLOBAL_STR(REMOTE_ADDR);
878 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR);
879 CONST_GLOBAL_STR(REQUEST_METHOD);
880 CONST_GLOBAL_STR(PATH_INFO);
881 CONST_GLOBAL_STR(QUERY_STRING);
882 CONST_GLOBAL_STR(REQUEST_URI);
883 CONST_GLOBAL_STR(HTTP_VERSION);
884 CONST_GLOBAL_STR2(rack_errors, "rack.errors");
885 CONST_GLOBAL_STR2(rack_input, "rack.input");
886 CONST_GLOBAL_STR2(rack_multithread, "rack.multithread");
887 CONST_GLOBAL_STR2(dash, "-");
888 CONST_GLOBAL_STR2(space, " ");
889 CONST_GLOBAL_STR2(question_mark, "?");
890 CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash");
892 tmp = rb_const_get(rb_cObject, rb_intern("Rack"));
893 tmp = rb_const_get(tmp, rb_intern("Utils"));
894 cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash"));
895 cToPath = rb_const_get(cClogger, rb_intern("ToPath"));
896 rb_define_method(cToPath, "to_path", to_path, 0);