16 #ifndef _POSIX_C_SOURCE
17 # define _POSIX_C_SOURCE 200112L
21 #include <stdio.h> /* snprintf */
22 #include "ruby_1_9_compat.h"
23 #include "broken_system_compat.h"
24 #include "blocking_helpers.h"
27 * Availability of a monotonic clock needs to be detected at runtime
28 * since we could've been built on a different system than we're run
31 static clockid_t hopefully_CLOCK_MONOTONIC
;
33 static void check_clock(void)
37 hopefully_CLOCK_MONOTONIC
= CLOCK_MONOTONIC
;
39 /* we can't check this reliably at compile time */
40 if (clock_gettime(CLOCK_MONOTONIC
, &now
) == 0)
43 if (clock_gettime(CLOCK_REALTIME
, &now
) == 0) {
44 hopefully_CLOCK_MONOTONIC
= CLOCK_REALTIME
;
45 rb_warn("CLOCK_MONOTONIC not available, "
46 "falling back to CLOCK_REALTIME");
48 rb_warn("clock_gettime() totally broken, " \
49 "falling back to pure Ruby Clogger");
50 rb_raise(rb_eLoadError
, "clock_gettime() broken");
53 static void clock_diff(struct timespec
*a
, const struct timespec
*b
)
55 a
->tv_sec
-= b
->tv_sec
;
56 a
->tv_nsec
-= b
->tv_nsec
;
59 a
->tv_nsec
+= 1000000000;
63 /* give GCC hints for better branch prediction
64 * (we layout branches so that ASCII characters are handled faster) */
65 #if defined(__GNUC__) && (__GNUC__ >= 3)
66 # define likely(x) __builtin_expect (!!(x), 1)
67 # define unlikely(x) __builtin_expect (!!(x), 0)
69 # define unlikely(x) (x)
70 # define likely(x) (x)
86 enum clogger_special
{
87 CL_SP_body_bytes_sent
= 0,
91 CL_SP_response_length
,
113 off_t body_bytes_sent
;
114 struct timespec ts_start
;
119 int reentrant
; /* tri-state, -1:auto, 1/0 true/false */
129 static ID sq_brace_id
;
131 static ID to_path_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
;
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
)
163 return !!(c
== '\'' || c
== '"' || c
<= 0x1f || c
>= 0x7f);
166 /* we are encoding-agnostic, clients can send us all sorts of junk */
167 static VALUE
byte_xs(VALUE obj
)
169 static const char esc
[] = "0123456789ABCDEF";
170 unsigned char *new_ptr
;
171 VALUE from
= rb_obj_as_string(obj
);
172 const unsigned char *ptr
= (const unsigned char *)RSTRING_PTR(from
);
173 long len
= RSTRING_LEN(from
);
177 for (; --len
>= 0; ptr
++) {
180 if (unlikely(need_escape(c
)))
181 new_len
+= 3; /* { '\', 'x', 'X', 'X' } */
184 len
= RSTRING_LEN(from
);
188 rv
= rb_str_new(NULL
, new_len
);
189 new_ptr
= (unsigned char *)RSTRING_PTR(rv
);
190 ptr
= (const unsigned char *)RSTRING_PTR(from
);
191 for (; --len
>= 0; ptr
++) {
194 if (unlikely(need_escape(c
))) {
197 *new_ptr
++ = esc
[c
>> 4];
198 *new_ptr
++ = esc
[c
& 0xf];
203 assert(RSTRING_PTR(rv
)[RSTRING_LEN(rv
)] == '\0');
209 static void clogger_mark(void *ptr
)
211 struct clogger
*c
= ptr
;
214 rb_gc_mark(c
->fmt_ops
);
215 rb_gc_mark(c
->logger
);
216 rb_gc_mark(c
->log_buf
);
218 rb_gc_mark(c
->cookies
);
219 rb_gc_mark(c
->status
);
220 rb_gc_mark(c
->headers
);
224 static VALUE
clogger_alloc(VALUE klass
)
228 return Data_Make_Struct(klass
, struct clogger
, clogger_mark
, -1, c
);
231 static struct clogger
*clogger_get(VALUE self
)
235 Data_Get_Struct(self
, struct clogger
, c
);
240 /* only for writing to regular files, not stupid crap like NFS */
241 static void write_full(int fd
, const void *buf
, size_t count
)
244 unsigned long ubuf
= (unsigned long)buf
;
247 r
= write(fd
, (void *)ubuf
, count
);
249 if ((size_t)r
== count
) { /* overwhelmingly likely */
255 if (errno
== EINTR
|| errno
== EAGAIN
)
256 continue; /* poor souls on NFS and like: */
259 rb_sys_fail("write");
265 * allow us to use write_full() iff we detect a blocking file
266 * descriptor that wouldn't play nicely with Ruby threading/fibers
268 static int raw_fd(VALUE my_fd
)
270 #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
278 flags
= fcntl(fd
, F_GETFL
);
280 rb_sys_fail("fcntl");
282 if (flags
& O_NONBLOCK
) {
285 if (fstat(fd
, &sb
) < 0)
288 /* O_NONBLOCK is no-op for regular files: */
289 if (! S_ISREG(sb
.st_mode
))
293 #else /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
295 #endif /* platforms w/o fcntl/F_GETFL/O_NONBLOCK */
299 static VALUE
clogger_reentrant(VALUE self
)
301 return clogger_get(self
)->reentrant
== 0 ? Qfalse
: Qtrue
;
305 static VALUE
clogger_wrap_body(VALUE self
)
307 return clogger_get(self
)->wrap_body
== 0 ? Qfalse
: Qtrue
;
310 static void append_status(struct clogger
*c
)
312 char buf
[sizeof("999")];
314 VALUE status
= c
->status
;
316 if (TYPE(status
) != T_FIXNUM
) {
317 status
= rb_funcall(status
, to_i_id
, 0);
318 /* no way it's a valid status code (at least not HTTP/1.1) */
319 if (TYPE(status
) != T_FIXNUM
) {
320 rb_str_buf_append(c
->log_buf
, g_dash
);
325 nr
= FIX2INT(status
);
326 if (nr
>= 100 && nr
<= 999) {
327 nr
= snprintf(buf
, sizeof(buf
), "%03d", nr
);
329 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
331 /* raise?, swap for 500? */
332 rb_str_buf_append(c
->log_buf
, g_dash
);
336 /* this is Rack 1.0.0-compatible, won't try to parse commas in XFF */
337 static void append_ip(struct clogger
*c
)
340 VALUE tmp
= rb_hash_aref(env
, g_HTTP_X_FORWARDED_FOR
);
343 /* can't be faked on any real server, so no escape */
344 tmp
= rb_hash_aref(env
, g_REMOTE_ADDR
);
350 rb_str_buf_append(c
->log_buf
, tmp
);
353 static void append_body_bytes_sent(struct clogger
*c
)
355 char buf
[(sizeof(off_t
) * 8) / 3 + 1];
356 const char *fmt
= sizeof(off_t
) == sizeof(long) ? "%ld" : "%lld";
357 int nr
= snprintf(buf
, sizeof(buf
), fmt
, c
->body_bytes_sent
);
359 assert(nr
> 0 && nr
< (int)sizeof(buf
));
360 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
363 static void append_ts(struct clogger
*c
, VALUE op
, struct timespec
*ts
)
365 char buf
[sizeof(".000000") + ((sizeof(ts
->tv_sec
) * 8) / 3)];
367 char *fmt
= RSTRING_PTR(rb_ary_entry(op
, 1));
368 int ndiv
= NUM2INT(rb_ary_entry(op
, 2));
369 int usec
= ts
->tv_nsec
/ 1000;
371 nr
= snprintf(buf
, sizeof(buf
), fmt
,
372 (int)ts
->tv_sec
, (int)(usec
/ ndiv
));
373 assert(nr
> 0 && nr
< (int)sizeof(buf
));
374 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
377 static void append_request_time_fmt(struct clogger
*c
, VALUE op
)
381 clock_gettime(hopefully_CLOCK_MONOTONIC
, &now
);
382 clock_diff(&now
, &c
->ts_start
);
383 append_ts(c
, op
, &now
);
386 static void append_time_fmt(struct clogger
*c
, VALUE op
)
389 int r
= clock_gettime(CLOCK_REALTIME
, &now
);
391 if (unlikely(r
!= 0))
392 rb_sys_fail("clock_gettime(CLOCK_REALTIME)");
393 append_ts(c
, op
, &now
);
396 static void append_request_uri(struct clogger
*c
)
400 tmp
= rb_hash_aref(c
->env
, g_REQUEST_URI
);
402 tmp
= rb_hash_aref(c
->env
, g_PATH_INFO
);
404 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
405 tmp
= rb_hash_aref(c
->env
, g_QUERY_STRING
);
406 if (!NIL_P(tmp
) && RSTRING_LEN(tmp
) != 0) {
407 rb_str_buf_append(c
->log_buf
, g_question_mark
);
408 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
411 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
415 static void append_request(struct clogger
*c
)
419 /* REQUEST_METHOD doesn't need escaping, Rack::Lint governs it */
420 tmp
= rb_hash_aref(c
->env
, g_REQUEST_METHOD
);
422 rb_str_buf_append(c
->log_buf
, tmp
);
424 rb_str_buf_append(c
->log_buf
, g_space
);
426 append_request_uri(c
);
428 /* HTTP_VERSION can be injected by malicious clients */
429 tmp
= rb_hash_aref(c
->env
, g_HTTP_VERSION
);
431 rb_str_buf_append(c
->log_buf
, g_space
);
432 rb_str_buf_append(c
->log_buf
, byte_xs(tmp
));
436 static void append_request_length(struct clogger
*c
)
438 VALUE tmp
= rb_hash_aref(c
->env
, g_rack_input
);
440 rb_str_buf_append(c
->log_buf
, g_dash
);
442 tmp
= rb_funcall(tmp
, size_id
, 0);
443 rb_str_buf_append(c
->log_buf
, rb_funcall(tmp
, to_s_id
, 0));
447 static long local_gmtoffset(struct tm
*tm
)
449 time_t t
= time(NULL
);
455 * HAVE_STRUCT_TM_TM_GMTOFF may be defined in Ruby headers
456 * HAVE_ST_TM_GMTOFF is defined ourselves.
458 #if defined(HAVE_STRUCT_TM_TM_GMTOFF) || defined(HAVE_ST_TM_GMTOFF)
459 return tm
->tm_gmtoff
/ 60;
461 return -(tm
->tm_isdst
? timezone
- 3600 : timezone
) / 60;
465 static void append_time_iso8601(struct clogger
*c
)
467 char buf
[sizeof("1970-01-01T00:00:00+00:00")];
470 long gmtoff
= local_gmtoffset(&tm
);
472 nr
= snprintf(buf
, sizeof(buf
),
473 "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
474 tm
.tm_year
+ 1900, tm
.tm_mon
+ 1,
475 tm
.tm_mday
, tm
.tm_hour
,
476 tm
.tm_min
, tm
.tm_sec
,
477 gmtoff
< 0 ? '-' : '+',
478 abs(gmtoff
/ 60), abs(gmtoff
% 60));
479 assert(nr
== (sizeof(buf
) - 1) && "snprintf fail");
480 rb_str_buf_cat(c
->log_buf
, buf
, sizeof(buf
) - 1);
483 static const char months
[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
484 "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
486 static void append_time_local(struct clogger
*c
)
488 char buf
[sizeof("01/Jan/1970:00:00:00 +0000")];
491 long gmtoff
= local_gmtoffset(&tm
);
493 nr
= snprintf(buf
, sizeof(buf
),
494 "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
495 tm
.tm_mday
, months
+ (tm
.tm_mon
* sizeof("Jan")),
496 tm
.tm_year
+ 1900, tm
.tm_hour
,
497 tm
.tm_min
, tm
.tm_sec
,
498 gmtoff
< 0 ? '-' : '+',
499 abs(gmtoff
/ 60), abs(gmtoff
% 60));
500 assert(nr
== (sizeof(buf
) - 1) && "snprintf fail");
501 rb_str_buf_cat(c
->log_buf
, buf
, sizeof(buf
) - 1);
504 static void append_time_utc(struct clogger
*c
)
506 char buf
[sizeof("01/Jan/1970:00:00:00 +0000")];
509 time_t t
= time(NULL
);
512 nr
= snprintf(buf
, sizeof(buf
),
513 "%02d/%s/%d:%02d:%02d:%02d +0000",
514 tm
.tm_mday
, months
+ (tm
.tm_mon
* sizeof("Jan")),
515 tm
.tm_year
+ 1900, tm
.tm_hour
,
516 tm
.tm_min
, tm
.tm_sec
);
517 assert(nr
== (sizeof(buf
) - 1) && "snprintf fail");
518 rb_str_buf_cat(c
->log_buf
, buf
, sizeof(buf
) - 1);
522 append_time(struct clogger
*c
, enum clogger_opcode op
, VALUE fmt
, VALUE buf
)
524 char *buf_ptr
= RSTRING_PTR(buf
);
525 size_t buf_size
= RSTRING_LEN(buf
) + 1; /* "\0" */
528 time_t t
= time(NULL
);
530 if (op
== CL_OP_TIME_LOCAL
)
531 localtime_r(&t
, &tmp
);
532 else if (op
== CL_OP_TIME_UTC
)
535 assert(0 && "unknown op");
537 nr
= strftime(buf_ptr
, buf_size
, RSTRING_PTR(fmt
), &tmp
);
538 assert(nr
< buf_size
&& "time format too small!");
539 rb_str_buf_cat(c
->log_buf
, buf_ptr
, nr
);
542 static void append_pid(struct clogger
*c
)
544 char buf
[(sizeof(pid_t
) * 8) / 3 + 1];
545 int nr
= snprintf(buf
, sizeof(buf
), "%d", (int)getpid());
547 assert(nr
> 0 && nr
< (int)sizeof(buf
));
548 rb_str_buf_cat(c
->log_buf
, buf
, nr
);
551 static void append_eval(struct clogger
*c
, VALUE str
)
554 VALUE rv
= rb_eval_string_protect(RSTRING_PTR(str
), &state
);
556 rv
= state
== 0 ? rb_obj_as_string(rv
) : g_dash
;
557 rb_str_buf_append(c
->log_buf
, rv
);
560 static void append_cookie(struct clogger
*c
, VALUE key
)
564 if (c
->cookies
== Qfalse
)
565 c
->cookies
= rb_hash_aref(c
->env
, g_rack_request_cookie_hash
);
567 if (NIL_P(c
->cookies
)) {
570 cookie
= rb_hash_aref(c
->cookies
, key
);
571 cookie
= NIL_P(cookie
) ? g_dash
: byte_xs(cookie
);
573 rb_str_buf_append(c
->log_buf
, cookie
);
576 static void append_request_env(struct clogger
*c
, VALUE key
)
578 VALUE tmp
= rb_hash_aref(c
->env
, key
);
580 tmp
= NIL_P(tmp
) ? g_dash
: byte_xs(tmp
);
581 rb_str_buf_append(c
->log_buf
, tmp
);
584 static void append_response(struct clogger
*c
, VALUE key
)
588 assert(rb_obj_is_kind_of(c
->headers
, cHeaderHash
) && "not HeaderHash");
590 v
= rb_funcall(c
->headers
, sq_brace_id
, 1, key
);
591 v
= NIL_P(v
) ? g_dash
: byte_xs(v
);
592 rb_str_buf_append(c
->log_buf
, v
);
595 static void special_var(struct clogger
*c
, enum clogger_special var
)
598 case CL_SP_body_bytes_sent
:
599 append_body_bytes_sent(c
);
607 case CL_SP_request_length
:
608 append_request_length(c
);
610 case CL_SP_response_length
:
611 if (c
->body_bytes_sent
== 0)
612 rb_str_buf_append(c
->log_buf
, g_dash
);
614 append_body_bytes_sent(c
);
622 case CL_SP_request_uri
:
623 append_request_uri(c
);
625 case CL_SP_time_iso8601
:
626 append_time_iso8601(c
);
628 case CL_SP_time_local
:
629 append_time_local(c
);
636 static VALUE
cwrite(struct clogger
*c
)
638 const VALUE ops
= c
->fmt_ops
;
640 long len
= RARRAY_LEN(ops
);
641 VALUE dst
= c
->log_buf
;
643 rb_str_set_len(dst
, 0);
645 for (i
= 0; i
< len
; i
++) {
646 VALUE op
= rb_ary_entry(ops
, i
);
647 enum clogger_opcode opcode
= FIX2INT(rb_ary_entry(op
, 0));
648 VALUE op1
= rb_ary_entry(op
, 1);
652 rb_str_buf_append(dst
, op1
);
655 append_request_env(c
, op1
);
658 append_response(c
, op1
);
661 special_var(c
, FIX2INT(op1
));
666 case CL_OP_TIME_LOCAL
:
667 case CL_OP_TIME_UTC
: {
668 VALUE arg2
= rb_ary_entry(op
, 2);
669 append_time(c
, opcode
, op1
, arg2
);
672 case CL_OP_REQUEST_TIME
:
673 append_request_time_fmt(c
, op
);
676 append_time_fmt(c
, op
);
679 append_cookie(c
, op1
);
685 write_full(c
->fd
, RSTRING_PTR(dst
), RSTRING_LEN(dst
));
687 VALUE logger
= c
->logger
;
690 logger
= rb_hash_aref(c
->env
, g_rack_errors
);
691 rb_funcall(logger
, write_id
, 1, dst
);
693 rb_funcall(logger
, ltlt_id
, 1, dst
);
700 static VALUE
clogger_write(VALUE self
)
702 return cwrite(clogger_get(self
));
705 static void init_logger(struct clogger
*c
, VALUE path
)
709 if (!NIL_P(path
) && !NIL_P(c
->logger
))
710 rb_raise(rb_eArgError
, ":logger and :path are independent");
712 VALUE ab
= rb_str_new2("ab");
713 id
= rb_intern("open");
714 c
->logger
= rb_funcall(rb_cFile
, id
, 2, path
, ab
);
717 id
= rb_intern("sync=");
718 if (rb_respond_to(c
->logger
, id
))
719 rb_funcall(c
->logger
, id
, 1, Qtrue
);
721 id
= rb_intern("fileno");
722 if (rb_respond_to(c
->logger
, id
))
723 c
->fd
= raw_fd(rb_funcall(c
->logger
, id
, 0));
728 * Clogger.new(app, :logger => $stderr, :format => string) => obj
730 * Creates a new Clogger object that wraps +app+. +:logger+ may
731 * be any object that responds to the "<<" method with a string argument.
732 * Instead of +:logger+, +:path+ may be specified to be a :path of a File
733 * that will be opened in append mode.
735 static VALUE
clogger_init(int argc
, VALUE
*argv
, VALUE self
)
737 struct clogger
*c
= clogger_get(self
);
739 VALUE fmt
= rb_const_get(mFormat
, rb_intern("Common"));
741 rb_scan_args(argc
, argv
, "11", &c
->app
, &o
);
744 c
->reentrant
= -1; /* auto-detect */
746 if (TYPE(o
) == T_HASH
) {
749 tmp
= rb_hash_aref(o
, ID2SYM(rb_intern("path")));
750 c
->logger
= rb_hash_aref(o
, ID2SYM(rb_intern("logger")));
753 tmp
= rb_hash_aref(o
, ID2SYM(rb_intern("format")));
757 tmp
= rb_hash_aref(o
, ID2SYM(rb_intern("reentrant")));
767 rb_raise(rb_eArgError
, ":reentrant must be boolean");
772 c
->fmt_ops
= rb_funcall(self
, rb_intern("compile_format"), 2, fmt
, o
);
774 if (Qtrue
== rb_funcall(self
, rb_intern("need_response_headers?"),
777 if (Qtrue
== rb_funcall(self
, rb_intern("need_wrap_body?"),
784 static VALUE
body_iter_i(VALUE str
, VALUE self
)
786 struct clogger
*c
= clogger_get(self
);
788 str
= rb_obj_as_string(str
);
789 c
->body_bytes_sent
+= RSTRING_LEN(str
);
791 return rb_yield(str
);
794 static VALUE
body_close(VALUE self
)
796 struct clogger
*c
= clogger_get(self
);
798 if (rb_respond_to(c
->body
, close_id
))
799 return rb_funcall(c
->body
, close_id
, 0);
805 * clogger.each { |part| socket.write(part) }
807 * Delegates the body#each call to the underlying +body+ object
808 * while tracking the number of bytes yielded. This will log
811 static VALUE
clogger_each(VALUE self
)
813 struct clogger
*c
= clogger_get(self
);
816 c
->body_bytes_sent
= 0;
817 rb_iterate(rb_each
, c
->body
, body_iter_i
, self
);
826 * Delegates the body#close call to the underlying +body+ object.
827 * This is only used when Clogger is wrapping the +body+ of a Rack
828 * response and should be automatically called by the web server.
830 static VALUE
clogger_close(VALUE self
)
833 return rb_ensure(body_close
, self
, clogger_write
, self
);
837 static VALUE
clogger_fileno(VALUE self
)
839 struct clogger
*c
= clogger_get(self
);
841 return c
->fd
< 0 ? Qnil
: INT2NUM(c
->fd
);
844 static VALUE
ccall(struct clogger
*c
, VALUE env
)
848 clock_gettime(hopefully_CLOCK_MONOTONIC
, &c
->ts_start
);
851 rv
= rb_funcall(c
->app
, call_id
, 1, env
);
852 if (TYPE(rv
) == T_ARRAY
&& RARRAY_LEN(rv
) == 3) {
853 c
->status
= rb_ary_entry(rv
, 0);
854 c
->headers
= rb_ary_entry(rv
, 1);
855 c
->body
= rb_ary_entry(rv
, 2);
859 ! rb_obj_is_kind_of(c
->headers
, cHeaderHash
)) {
860 c
->headers
= rb_funcall(cHeaderHash
, new_id
, 1,
862 rb_ary_store(rv
, 1, c
->headers
);
865 VALUE tmp
= rb_inspect(rv
);
867 c
->status
= INT2FIX(500);
868 c
->headers
= c
->body
= rb_ary_new();
870 rb_raise(rb_eTypeError
,
871 "app response not a 3 element Array: %s",
881 * clogger.call(env) => [ status, headers, body ]
883 * calls the wrapped Rack application with +env+, returns the
884 * [status, headers, body ] tuplet required by Rack.
886 static VALUE
clogger_call(VALUE self
, VALUE env
)
888 struct clogger
*c
= clogger_get(self
);
891 env
= rb_check_convert_type(env
, T_HASH
, "Hash", "to_hash");
894 /* XXX: we assume the existence of the GVL here: */
895 if (c
->reentrant
< 0) {
896 VALUE tmp
= rb_hash_aref(env
, g_rack_multithread
);
897 c
->reentrant
= Qfalse
== tmp
? 0 : 1;
901 self
= rb_obj_dup(self
);
902 c
= clogger_get(self
);
906 assert(!OBJ_FROZEN(rv
) && "frozen response array");
907 rb_ary_store(rv
, 2, self
);
918 static void duplicate_buffers(VALUE ops
)
921 long len
= RARRAY_LEN(ops
);
923 for (i
= 0; i
< len
; i
++) {
924 VALUE op
= rb_ary_entry(ops
, i
);
925 enum clogger_opcode opcode
= FIX2INT(rb_ary_entry(op
, 0));
927 if (opcode
== CL_OP_TIME_LOCAL
|| opcode
== CL_OP_TIME_UTC
) {
928 VALUE buf
= rb_ary_entry(op
, 2);
929 Check_Type(buf
, T_STRING
);
930 buf
= rb_str_dup(buf
);
931 rb_str_modify(buf
); /* trigger copy-on-write */
932 rb_ary_store(op
, 2, buf
);
938 static VALUE
clogger_init_copy(VALUE clone
, VALUE orig
)
940 struct clogger
*a
= clogger_get(orig
);
941 struct clogger
*b
= clogger_get(clone
);
943 memcpy(b
, a
, sizeof(struct clogger
));
945 duplicate_buffers(b
->fmt_ops
);
950 #define CONST_GLOBAL_STR2(var, val) do { \
951 g_##var = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
952 rb_global_variable(&g_##var); \
955 #define CONST_GLOBAL_STR(val) CONST_GLOBAL_STR2(val, #val)
959 * clogger.respond_to?(:to_path) => true or false
960 * clogger.respond_to?(:close) => true
962 * used to delegate +:to_path+ checks for Rack webservers that optimize
963 * static file serving
965 static VALUE
respond_to(VALUE self
, VALUE method
)
967 struct clogger
*c
= clogger_get(self
);
968 ID id
= rb_to_id(method
);
972 return rb_respond_to(c
->body
, id
);
979 * used to proxy +:to_path+ method calls to the wrapped response body.
981 static VALUE
to_path(VALUE self
)
983 struct clogger
*c
= clogger_get(self
);
986 VALUE path
= rb_funcall(c
->body
, to_path_id
, 0);
987 const char *cpath
= StringValueCStr(path
);
991 * Rainbows! can use "/dev/fd/%u" in to_path output to avoid
992 * extra open() syscalls, too.
994 if (sscanf(cpath
, "/dev/fd/%u", &devfd
) == 1)
995 rv
= fstat((int)devfd
, &sb
);
997 rv
= stat(cpath
, &sb
);
1000 * calling this method implies the web server will bypass
1001 * the each method where body_bytes_sent is calculated,
1002 * so we stat and set that value here.
1004 c
->body_bytes_sent
= rv
== 0 ? sb
.st_size
: 0;
1009 static VALUE
body(VALUE self
)
1011 return clogger_get(self
)->body
;
1014 void Init_clogger_ext(void)
1020 write_id
= rb_intern("write");
1021 ltlt_id
= rb_intern("<<");
1022 call_id
= rb_intern("call");
1023 close_id
= rb_intern("close");
1024 to_i_id
= rb_intern("to_i");
1025 to_s_id
= rb_intern("to_s");
1026 size_id
= rb_intern("size");
1027 sq_brace_id
= rb_intern("[]");
1028 new_id
= rb_intern("new");
1029 to_path_id
= rb_intern("to_path");
1030 respond_to_id
= rb_intern("respond_to?");
1031 cClogger
= rb_define_class("Clogger", rb_cObject
);
1032 mFormat
= rb_define_module_under(cClogger
, "Format");
1033 rb_define_alloc_func(cClogger
, clogger_alloc
);
1034 rb_define_method(cClogger
, "initialize", clogger_init
, -1);
1035 rb_define_method(cClogger
, "initialize_copy", clogger_init_copy
, 1);
1036 rb_define_method(cClogger
, "call", clogger_call
, 1);
1037 rb_define_method(cClogger
, "each", clogger_each
, 0);
1038 rb_define_method(cClogger
, "close", clogger_close
, 0);
1039 rb_define_method(cClogger
, "fileno", clogger_fileno
, 0);
1040 rb_define_method(cClogger
, "wrap_body?", clogger_wrap_body
, 0);
1041 rb_define_method(cClogger
, "reentrant?", clogger_reentrant
, 0);
1042 rb_define_method(cClogger
, "to_path", to_path
, 0);
1043 rb_define_method(cClogger
, "respond_to?", respond_to
, 1);
1044 rb_define_method(cClogger
, "body", body
, 0);
1045 CONST_GLOBAL_STR(REMOTE_ADDR
);
1046 CONST_GLOBAL_STR(HTTP_X_FORWARDED_FOR
);
1047 CONST_GLOBAL_STR(REQUEST_METHOD
);
1048 CONST_GLOBAL_STR(PATH_INFO
);
1049 CONST_GLOBAL_STR(QUERY_STRING
);
1050 CONST_GLOBAL_STR(REQUEST_URI
);
1051 CONST_GLOBAL_STR(HTTP_VERSION
);
1052 CONST_GLOBAL_STR2(rack_errors
, "rack.errors");
1053 CONST_GLOBAL_STR2(rack_input
, "rack.input");
1054 CONST_GLOBAL_STR2(rack_multithread
, "rack.multithread");
1055 CONST_GLOBAL_STR2(dash
, "-");
1056 CONST_GLOBAL_STR2(space
, " ");
1057 CONST_GLOBAL_STR2(question_mark
, "?");
1058 CONST_GLOBAL_STR2(rack_request_cookie_hash
, "rack.request.cookie_hash");
1060 tmp
= rb_const_get(rb_cObject
, rb_intern("Rack"));
1061 tmp
= rb_const_get(tmp
, rb_intern("Utils"));
1062 cHeaderHash
= rb_const_get(tmp
, rb_intern("HeaderHash"));