1 # -*- encoding: binary -*-
4 # Not at all optimized for performance, this was written based on
5 # the original C extension code so it's not very Ruby-ish...
8 def initialize(app, opts = {})
10 @logger = opts[:logger]
11 @fmt_ops = compile_format(opts[:format] || Format::Common)
12 @wrap_body = need_wrap_body?(@fmt_ops)
20 unless resp.instance_of?(Array) && resp.size == 3
22 raise TypeError, "app response not a 3 element Array: #{resp.inspect}"
24 status, headers, body = resp
26 @reentrant = env['rack.multithread']
27 @env, @status, @headers, @body = env, status, headers, body
28 return [ status, headers, reentrant? ? self.dup : self ]
30 log(env, status, headers)
31 [ status, headers, body ]
37 @body_bytes_sent += part.size
41 log(@env, @status, @headers)
57 @logger.fileno rescue nil
64 s.force_encoding(Encoding::BINARY) if defined?(Encoding::BINARY)
65 s.gsub!(/(['"\x00-\x1f])/) { |x| "\\x#{$1.unpack('H2').first.upcase}" }
69 SPECIAL_RMAP = SPECIAL_VARS.inject([]) { |ary, (k,v)| ary[v] = k; ary }
72 ru = env['REQUEST_URI'] and return byte_xs(ru)
73 qs = env['QUERY_STRING']
74 qs.empty? or qs = "?#{byte_xs(qs)}"
75 "#{byte_xs(env['PATH_INFO'])}#{qs}"
78 def special_var(special_nr, env, status, headers)
79 case SPECIAL_RMAP[special_nr]
84 status >= 100 && status <= 999 ? ('%03d' % status) : '-'
86 version = env['HTTP_VERSION'] and version = " #{byte_xs(version)}"
87 qs = env['QUERY_STRING']
88 qs.empty? or qs = "?#{byte_xs(qs)}"
89 "#{env['REQUEST_METHOD']} " \
90 "#{request_uri(env)}#{version}"
94 env['rack.input'].size.to_s
96 @body_bytes_sent == 0 ? '-' : @body_bytes_sent.to_s
98 xff = env['HTTP_X_FORWARDED_FOR'] and return byte_xs(xff)
99 env['REMOTE_ADDR'] || '-'
103 raise "EDOOFUS #{special_nr}"
107 def time_format(sec, usec, format, div)
108 format % [ sec, usec / div ]
111 def log(env, status, headers)
112 (@logger || env['rack.errors']) << @fmt_ops.map { |op|
114 when OP_LITERAL; op[1]
115 when OP_REQUEST; byte_xs(env[op[1]] || "-")
116 when OP_RESPONSE; byte_xs(get_sent_header(headers, op[1]))
117 when OP_SPECIAL; special_var(op[1], env, status, headers)
118 when OP_EVAL; eval(op[1]).to_s rescue "-"
119 when OP_TIME_LOCAL; Time.now.strftime(op[1])
120 when OP_TIME_UTC; Time.now.utc.strftime(op[1])
122 t = Time.now - @start
123 time_format(t.to_i, (t - t.to_i) * 1000000, op[1], op[2])
126 time_format(t.sec, t.usec, op[1], op[2])
128 (env['rack.request.cookie_hash'][op[1]] rescue "-") || "-"
130 raise "EDOOFUS #{op.inspect}"
135 def get_sent_header(headers, match)
136 headers.each do |pair|
137 Array === pair && pair.size >= 2 or
138 raise TypeError, "headers not returning pairs"
140 match == key.downcase and return value