racknga 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,17 @@
1
1
  h1. NEWS
2
2
 
3
+ h2. 0.9.3: 2011-11-12
4
+
5
+ h3. Improvments
6
+
7
+ * [access-log-parser] Supported Apache log.
8
+ * [cache] Fixed unknown name errors.
9
+ * [cache] Fixed max age.
10
+ * [nginx] Added NginxRawURI middleware.
11
+ * [instance-name] Added branch name and Ruby version.
12
+ * [exception-mail-notifier] Used #message instead of #to_s.
13
+ * [logger] Logged also X-Runtime.
14
+
3
15
  h2. 0.9.2: 2011-08-07
4
16
 
5
17
  h3. Improvments
@@ -20,9 +20,10 @@ require 'rack'
20
20
 
21
21
  require 'racknga/version'
22
22
  require 'racknga/utils'
23
- require "racknga/nginx_access_log_parser"
23
+ require "racknga/access_log_parser"
24
24
  require 'racknga/middleware/deflater'
25
25
  require 'racknga/middleware/exception_notifier'
26
26
  require 'racknga/middleware/jsonp'
27
27
  require 'racknga/middleware/range'
28
28
  require "racknga/middleware/instance_name"
29
+ require "racknga/middleware/nginx_raw_uri"
@@ -0,0 +1,147 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
4
+ # Copyright (C) 2011 Kouhei Sutou <kou@clear-code.com>
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+
20
+ require "racknga/log_entry"
21
+ require "racknga/reverse_line_reader"
22
+
23
+ module Racknga
24
+ # Supported formats:
25
+ # * combined (nginx's default format)
26
+ # * combined (Apache's predefined format)
27
+ # * combined_with_time_nginx (custom format with runtime)
28
+ # * combined_with_time_apache (custom format with runtime)
29
+ #
30
+ # Configurations:
31
+ # * combined
32
+ # * nginx
33
+ # log_format combined '$remote_addr - $remote_user [$time_local] '
34
+ # '"$request" $status $body_bytes_sent '
35
+ # '"$http_referer" "$http_user_agent"';
36
+ # access_log log/access.log combined
37
+ # * Apache
38
+ # CustomLog ${APACHE_LOG_DIR}/access.log combined
39
+ #
40
+ # * combined_with_time_nginx
41
+ # * nginx
42
+ # log_format combined_with_time '$remote_addr - $remote_user '
43
+ # '[$time_local, $upstream_http_x_runtime, $request_time] '
44
+ # '"$request" $status $body_bytes_sent '
45
+ # '"$http_referer" "$http_user_agent"';
46
+ # access_log log/access.log combined_with_time
47
+ #
48
+ # * combined_with_time_apache
49
+ # * Apache
50
+ # LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %{X-Runtime}o %D" combined_with_time
51
+ # CustomLog ${APACHE_LOG_DIR}/access.log combined_with_time
52
+ class AccessLogParser
53
+ include Enumerable
54
+
55
+ class FormatError < StandardError
56
+ end
57
+
58
+ def initialize(line_reader)
59
+ @line_reader = line_reader
60
+ end
61
+
62
+ def each
63
+ @line_reader.each do |line|
64
+ line.force_encoding("UTF-8")
65
+ yield(parse_line(line)) if line.valid_encoding?
66
+ end
67
+ end
68
+
69
+ private
70
+ REMOTE_ADDRESS = "[^\\x20]+"
71
+ REMOTE_USER = "[^\\x20]+"
72
+ TIME_LOCAL = "[^\\x20]+\\x20\\+\\d{4}"
73
+ RUNTIME = "(?:[\\d.]+|-)"
74
+ REQUEST_TIME = "[\\d.]+"
75
+ REQUEST = ".*?"
76
+ STATUS = "\\d{3}"
77
+ BODY_BYTES_SENT = "(?:\\d+|-)"
78
+ HTTP_REFERER = ".*?"
79
+ HTTP_USER_AGENT = "(?:\\\"|[^\"])*?"
80
+ COMBINED_FORMAT =
81
+ /^(#{REMOTE_ADDRESS})\x20
82
+ -\x20
83
+ (#{REMOTE_USER})\x20
84
+ \[(#{TIME_LOCAL})(?:,\x20(.+))?\]\x20+
85
+ "(#{REQUEST})"\x20
86
+ (#{STATUS})\x20
87
+ (#{BODY_BYTES_SENT})\x20
88
+ "(#{HTTP_REFERER})"\x20
89
+ "(#{HTTP_USER_AGENT})"
90
+ (?:\x20(.+))?$/x
91
+ def parse_line(line)
92
+ case line
93
+ when COMBINED_FORMAT
94
+ last_match = Regexp.last_match
95
+ options = {}
96
+ options[:remote_address] = last_match[1]
97
+ options[:remote_user] = last_match[2]
98
+ options[:time_local] = parse_local_time(last_match[3])
99
+ if last_match[4]
100
+ if /\A(#{RUNTIME}), (#{REQUEST_TIME})\z/ =~ last_match[4]
101
+ time_match = Regexp.last_match
102
+ options[:runtime] = time_match[1]
103
+ options[:request_time] = time_match[2]
104
+ else
105
+ message = "expected 'RUNTIME, REQUEST_TIME' time format: " +
106
+ "<#{last_match[4]}>: <#{line}>"
107
+ raise FormatError.new(message)
108
+ end
109
+ end
110
+ options[:request] = last_match[5]
111
+ options[:status] = last_match[6].to_i
112
+ options[:body_bytes_sent] = last_match[7]
113
+ options[:http_referer] = last_match[8]
114
+ options[:http_user_agent] = last_match[9]
115
+ if last_match[10]
116
+ if /\A(#{RUNTIME}) (#{REQUEST_TIME})\z/ =~ last_match[10]
117
+ time_match = Regexp.last_match
118
+ runtime = time_match[1]
119
+ request_time = time_match[2]
120
+ request_time = request_time.to_i * 0.000_001 if request_time
121
+ options[:runtime] = runtime
122
+ options[:request_time] = request_time
123
+ else
124
+ message = "expected 'RUNTIME REQUEST_TIME' time format: " +
125
+ "<#{last_match[10]}>: <#{line}>"
126
+ raise FormatError.new(message)
127
+ end
128
+ end
129
+ LogEntry.new(options)
130
+ else
131
+ raise FormatError.new("unsupported format log entry: <#{line}>")
132
+ end
133
+ end
134
+
135
+ def parse_local_time(token)
136
+ day, month, year, hour, minute, second, _time_zone = token.split(/[\/: ]/)
137
+ _ = _time_zone # FIXME: suppress a warning. :<
138
+ Time.local(year, month, day, hour, minute, second)
139
+ end
140
+ end
141
+
142
+ class ReversedAccessLogParser < AccessLogParser
143
+ def initialize(line_reader)
144
+ @line_reader = ReverseLineReader.new(line_reader)
145
+ end
146
+ end
147
+ end
@@ -53,17 +53,27 @@ module Racknga
53
53
  # process from your Rack application
54
54
  # process. (e.g. cron.) It's multi process safe.
55
55
  def purge_old_responses
56
- age_modulo = 2 ** 32
56
+ age_modulo = 2 ** 31 - 1
57
57
  age = configuration.age
58
58
  previous_age = (age - 1).modulo(age_modulo)
59
- configuration.age = (age + 1).modulo(age_modulo)
59
+ next_age = (age + 1).modulo(age_modulo)
60
60
 
61
61
  target_responses = responses.select do |record|
62
- record.age == previous_age
62
+ record.age == next_age
63
63
  end
64
64
  target_responses.each do |response|
65
65
  response.key.delete
66
66
  end
67
+ configuration.age = next_age
68
+
69
+ target_responses = responses.select do |record|
70
+ record.age <= previous_age
71
+ end
72
+ target_responses.each do |response|
73
+ response.key.delete
74
+ end
75
+
76
+ responses.defrag if responses.respond_to?(:defrag)
67
77
  end
68
78
 
69
79
  def ensure_database
@@ -149,7 +149,7 @@ Timestamp: #{Time.now.rfc2822}
149
149
  --
150
150
  URL: #{request.url}
151
151
  --
152
- #{exception.class}: #{exception}
152
+ #{exception.class}: #{exception.message}
153
153
  EOB
154
154
  end
155
155
 
@@ -95,6 +95,7 @@ module Racknga
95
95
  table.reference("path", "Paths")
96
96
  table.reference("user_agent", "UserAgents")
97
97
  table.float("runtime")
98
+ table.float("request_time")
98
99
  table.short_text("message", :compress => :zlib)
99
100
  end
100
101
 
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
4
+ # Copyright (C) 2011 Kouhei Sutou <kou@clear-code.com>
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+
20
+ module Racknga
21
+ class LogEntry
22
+ ATTRIBUTES = [
23
+ :remote_address,
24
+ :remote_user,
25
+ :time_local,
26
+ :runtime,
27
+ :request_time,
28
+ :request,
29
+ :status,
30
+ :body_bytes_sent,
31
+ :http_referer,
32
+ :http_user_agent,
33
+ ]
34
+
35
+ attr_reader(*ATTRIBUTES)
36
+ def initialize(options=nil)
37
+ options ||= {}
38
+ @remote_address = options[:remote_address]
39
+ @remote_user = normalize_string_value(options[:remote_user])
40
+ @time_local = options[:time_local] || Time.at(0)
41
+ @runtime = normalize_float_value(options[:runtime])
42
+ @request_time = normalize_float_value(options[:request_time])
43
+ @request = options[:request]
44
+ @status = options[:status]
45
+ @body_bytes_sent = normalize_int_value(options[:body_bytes_sent])
46
+ @http_referer = normalize_string_value(options[:http_referer])
47
+ @http_user_agent = normalize_string_value(options[:http_user_agent])
48
+ end
49
+
50
+ def attributes
51
+ ATTRIBUTES.collect do |attribute|
52
+ __send__(attribute)
53
+ end
54
+ end
55
+
56
+ def ==(other)
57
+ other.is_a?(self.class) and attributes == other.attributes
58
+ end
59
+
60
+ private
61
+ def normalize_string_value(value)
62
+ if value.nil? or value == "-"
63
+ nil
64
+ else
65
+ value.to_s
66
+ end
67
+ end
68
+
69
+ def normalize_float_value(value)
70
+ if value.nil?
71
+ value
72
+ else
73
+ value.to_f
74
+ end
75
+ end
76
+
77
+ def normalize_int_value(value)
78
+ if value.nil? or value == "-"
79
+ nil
80
+ else
81
+ value.to_i
82
+ end
83
+ end
84
+ end
85
+ end
@@ -221,9 +221,9 @@ module Racknga
221
221
  def log(tag, request)
222
222
  return unless Middleware.const_defined?(:Log)
223
223
  env = request.env
224
- logger = env[Middleware::Log::LOGGER_KEY]
224
+ logger = env[Middleware::Log::LOGGER]
225
225
  return if logger.nil?
226
- start_time = env[START_TIME_KEY]
226
+ start_time = env[START_TIME]
227
227
  runtime = Time.now - start_time
228
228
  logger.log("cache-#{tag}", request.fullpath, :runtime => runtime)
229
229
  end
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2010-2011 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -63,7 +63,7 @@ module Racknga
63
63
 
64
64
  # For Rack.
65
65
  def call(environment)
66
- if ie6?(environment)
66
+ if ie6?(environment) or not valid_accept_encoding?(environment)
67
67
  @application.call(environment)
68
68
  else
69
69
  @deflater.call(environment)
@@ -74,6 +74,16 @@ module Racknga
74
74
  def ie6?(environment)
75
75
  /MSIE 6.0;/ =~ (environment["HTTP_USER_AGENT"] || '')
76
76
  end
77
+
78
+ def valid_accept_encoding?(environment)
79
+ request = Rack::Request.new(environment)
80
+ begin
81
+ request.accept_encoding
82
+ true
83
+ rescue
84
+ false
85
+ end
86
+ end
77
87
  end
78
88
  end
79
89
  end
@@ -26,7 +26,6 @@ module Racknga
26
26
  #
27
27
  # Usage:
28
28
  # require "racknga"
29
- # require "racknga/middleware/exception_notifier"
30
29
  #
31
30
  # notifier_options = {
32
31
  # :subject_label => "[YourApplication]",
@@ -68,6 +68,23 @@ module Racknga
68
68
  `id --user --name`.strip
69
69
  end
70
70
 
71
+ CURRENT_BRANCH_MARKER = /\A\* /
72
+ def branch
73
+ current_branch = nil
74
+ `git branch -a`.each_line do |line|
75
+ case line
76
+ when CURRENT_BRANCH_MARKER
77
+ current_branch = line.sub(CURRENT_BRANCH_MARKER, "").strip
78
+ break
79
+ end
80
+ end
81
+ current_branch
82
+ end
83
+
84
+ def ruby
85
+ RUBY_DESCRIPTION
86
+ end
87
+
71
88
  private
72
89
  DEFAULT_HEADER_NAME = "X-Responsed-By"
73
90
  def header_name
@@ -83,9 +100,10 @@ module Racknga
83
100
  def construct_header
84
101
  format_header(format_application_name(application_name),
85
102
  format_version(version),
86
- format_revision(revision),
103
+ format_revision(branch, revision),
87
104
  format_server(server),
88
- format_user(user))
105
+ format_user(user),
106
+ format_ruby(ruby))
89
107
  end
90
108
 
91
109
  def format_header(*arguments)
@@ -104,9 +122,15 @@ module Racknga
104
122
  end
105
123
  end
106
124
 
107
- def format_revision(revision)
125
+ def format_revision(branch, revision)
108
126
  format_if_possible(revision) do
109
- "(at #{revision})"
127
+ "(at #{revision}#{format_branch(branch)})"
128
+ end
129
+ end
130
+
131
+ def format_branch(branch)
132
+ format_if_possible(branch) do
133
+ " (#{branch})"
110
134
  end
111
135
  end
112
136
 
@@ -122,12 +146,18 @@ module Racknga
122
146
  end
123
147
  end
124
148
 
149
+ def format_ruby(ruby)
150
+ format_if_possible(ruby) do
151
+ "with #{ruby}"
152
+ end
153
+ end
154
+
125
155
  def format_if_possible(data)
126
156
  if data and (data.respond_to?(:to_s) and not data.to_s.empty?)
127
- result = yield
157
+ yield
158
+ else
159
+ nil
128
160
  end
129
-
130
- result
131
161
  end
132
162
  end
133
163
  end
@@ -125,6 +125,7 @@ module Racknga
125
125
  def update_content_type(header_hash)
126
126
  content_type = header_hash["Content-Type"]
127
127
  media_type, parameters = content_type.split(/\s*;\s*/, 2)
128
+ _ = media_type # FIXME: suppress a warning. :<
128
129
  # We should use application/javascript not
129
130
  # text/javascript when all IE <= 8 are deprecated. :<
130
131
  updated_content_type = ["text/javascript", parameters].compact.join("; ")
@@ -70,9 +70,12 @@ module Racknga
70
70
  private
71
71
  def log(start_time, end_time, request, status, headers, body)
72
72
  request_time = end_time - start_time
73
+ runtime = headers["X-Runtime"]
74
+ runtime_in_float = nil
75
+ runtime_in_float = runtime.to_f if runtime
73
76
  length = headers["Content-Length"] || "-"
74
77
  length = "-" if length == "0"
75
- format = "%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\" %0.8f"
78
+ format = "%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\" %s %0.8f"
76
79
  message = format % [request.ip || "-",
77
80
  request.env["REMOTE_USER"] || "-",
78
81
  end_time.dup.utc.strftime("%d/%b/%Y:%H:%M:%S %z"),
@@ -83,12 +86,14 @@ module Racknga
83
86
  length,
84
87
  request.env["HTTP_REFERER"] || "-",
85
88
  request.user_agent || "-",
89
+ runtime || "-",
86
90
  request_time]
87
91
  @logger.log("access",
88
92
  request.fullpath,
89
93
  :message => message,
90
94
  :user_agent => request.user_agent,
91
- :runtime => request_time)
95
+ :runtime => runtime_in_float,
96
+ :request_time => request_time)
92
97
  end
93
98
 
94
99
  # @private
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+
19
+ module Racknga
20
+ module Middleware
21
+ # NOTE:
22
+ # This is a middleware that restores the unprocessed URI client requests
23
+ # as is. Usually, nginx-passenger stack unescapes percent encoding in URI
24
+ # and resolve relative paths (ie "." and "..").
25
+ # Most of time, processed URI isn't program. However, if you want to
26
+ # distinguish %2F (ie "/") from "/", it is.
27
+ #
28
+ # Passenger 3.x or later is required.
29
+ #
30
+ # Use this with following nginx configuration:
31
+ #
32
+ # ... {
33
+ # ...
34
+ # passenger_set_cgi_param HTTP_X_RAW_REQUEST_URI $request_uri;
35
+ # }
36
+ #
37
+ # Usage:
38
+ # require "racknga"
39
+ # use Racknga::Middleware::NginxRawURI
40
+ # run YourApplication
41
+ class NginxRawURI
42
+ RAW_REQUEST_URI_HEADER_NAME = "HTTP_X_RAW_REQUEST_URI"
43
+ def initialize(application)
44
+ @application = application
45
+ end
46
+
47
+ # For Rack.
48
+ def call(environment)
49
+ raw_uri = environment[RAW_REQUEST_URI_HEADER_NAME]
50
+
51
+ if raw_uri
52
+ restore_raw_uri(environment, raw_uri)
53
+ end
54
+
55
+ @application.call(environment)
56
+ end
57
+
58
+ private
59
+ def restore_raw_uri(environment, raw_uri)
60
+ environment["PATH_INFO"] = raw_uri.split("?").first
61
+ environment["REQUEST_URI"] = raw_uri
62
+ end
63
+ end
64
+ end
65
+ end
@@ -17,5 +17,5 @@
17
17
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
 
19
19
  module Racknga
20
- VERSION = '0.9.2'
20
+ VERSION = '0.9.3'
21
21
  end
@@ -19,30 +19,33 @@
19
19
 
20
20
  require 'stringio'
21
21
 
22
- module NginxAccessLogParserTests
22
+ module AccessLogParserTests
23
23
  module Data
24
24
  private
25
- def time_log_component
26
- times = ["03/Aug/2011:16:58:01 +0900", runtime, request_time].compact
27
- "[#{times.join(', ')}]"
25
+ def time_local
26
+ "03/Aug/2011:16:58:01 +0900"
27
+ end
28
+
29
+ def log_line(content)
30
+ content
28
31
  end
29
32
 
30
33
  def usual_log_line
31
- "127.0.0.1 - - #{time_log_component} " +
32
- "\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\""
34
+ log_line("127.0.0.1 - - #{time_log_component} " +
35
+ "\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
33
36
  end
34
37
 
35
38
  def usual_log_entry_options
36
39
  {
37
40
  :remote_address => "127.0.0.1",
38
- :remote_user => "-",
41
+ :remote_user => nil,
39
42
  :time_local => Time.local(2011, 8, 3, 16, 58, 1),
40
43
  :runtime => runtime,
41
44
  :request_time => request_time,
42
45
  :request => "GET / HTTP/1.1",
43
46
  :status => 200,
44
47
  :body_bytes_sent => 613,
45
- :http_referer => "-",
48
+ :http_referer => nil,
46
49
  :http_user_agent => "Ruby",
47
50
  }
48
51
  end
@@ -52,8 +55,8 @@ module NginxAccessLogParserTests
52
55
  end
53
56
 
54
57
  def not_found_log_line
55
- "127.0.0.1 - - #{time_log_component} " +
56
- "\"GET /the-truth.html HTTP/1.1\" 404 613 \"-\" \"Ruby\""
58
+ log_line("127.0.0.1 - - #{time_log_component} " +
59
+ "\"GET /the-truth.html HTTP/1.1\" 404 613 \"-\" \"Ruby\"")
57
60
  end
58
61
 
59
62
  def not_found_log_entry
@@ -71,8 +74,8 @@ module NginxAccessLogParserTests
71
74
  def valid_utf8_log_line
72
75
  path = utf8_path
73
76
 
74
- "127.0.0.1 - - #{time_log_component} " +
75
- "\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\""
77
+ log_line("127.0.0.1 - - #{time_log_component} " +
78
+ "\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
76
79
  end
77
80
 
78
81
  def valid_utf8_log_entry
@@ -90,8 +93,41 @@ module NginxAccessLogParserTests
90
93
  def invalid_utf8_log_line
91
94
  path = garbled_path
92
95
 
93
- "127.0.0.1 - - #{time_log_component} " +
94
- "\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\""
96
+ log_line("127.0.0.1 - - #{time_log_component} " +
97
+ "\"GET #{path} HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
98
+ end
99
+
100
+ def ipv6_log_line
101
+ log_line("::1 - - #{time_log_component} " +
102
+ "\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
103
+ end
104
+
105
+ def ipv6_log_entry
106
+ options = {
107
+ :remote_address => "::1",
108
+ }
109
+ create_log_entry(usual_log_entry_options.merge(options))
110
+ end
111
+
112
+ def apache_combined_log_line
113
+ log_line("127.0.0.1 - - #{time_log_component} " +
114
+ "\"GET / HTTP/1.1\" 200 613 \"-\" \"Ruby\"")
115
+ end
116
+
117
+ def apache_combined_log_entry
118
+ usual_log_entry
119
+ end
120
+
121
+ def no_body_bytes_sent_log_line
122
+ log_line("127.0.0.1 - - #{time_log_component} " +
123
+ "\"GET / HTTP/1.1\" 200 - \"-\" \"Ruby\"")
124
+ end
125
+
126
+ def no_body_bytes_sent_log_entry
127
+ options = {
128
+ :body_bytes_sent => nil,
129
+ }
130
+ create_log_entry(usual_log_entry_options.merge(options))
95
131
  end
96
132
 
97
133
  def bad_log_line
@@ -110,11 +146,11 @@ module NginxAccessLogParserTests
110
146
  end
111
147
 
112
148
  def create_log_parser(file)
113
- Racknga::NginxAccessLogParser.new(file)
149
+ Racknga::AccessLogParser.new(file)
114
150
  end
115
151
 
116
152
  def create_reversed_log_parser(file)
117
- Racknga::ReversedNginxAccessLogParser.new(file)
153
+ Racknga::ReversedAccessLogParser.new(file)
118
154
  end
119
155
 
120
156
  def join_lines(*lines)
@@ -137,18 +173,36 @@ module NginxAccessLogParserTests
137
173
  end
138
174
 
139
175
  module Tests
140
- def test_usual_nginx_access_log
176
+ def test_usual_log
141
177
  accesses = parse(join_lines(usual_log_line))
142
- access = accesses.first
143
- assert_equal(usual_log_entry, access)
178
+ assert_equal([usual_log_entry],
179
+ accesses)
144
180
  end
145
181
 
146
- def test_no_access_log
182
+ def test_ipv6_log
183
+ accesses = parse(join_lines(ipv6_log_line))
184
+ assert_equal([ipv6_log_entry],
185
+ accesses)
186
+ end
187
+
188
+ def test_apache_combined_log
189
+ accesses = parse(join_lines(apache_combined_log_line))
190
+ assert_equal([apache_combined_log_entry],
191
+ accesses)
192
+ end
193
+
194
+ def test_no_body_bytes_sent_log
195
+ accesses = parse(join_lines(no_body_bytes_sent_log_line))
196
+ assert_equal([no_body_bytes_sent_log_entry],
197
+ accesses)
198
+ end
199
+
200
+ def test_no_log
147
201
  accesses = parse(join_lines())
148
202
  assert_equal([], accesses)
149
203
  end
150
204
 
151
- def test_multiple_nginx_access_log
205
+ def test_multiple_logs
152
206
  accesses = parse(join_lines(usual_log_line,
153
207
  usual_log_line,
154
208
  not_found_log_line))
@@ -158,7 +212,7 @@ module NginxAccessLogParserTests
158
212
  accesses)
159
213
  end
160
214
 
161
- def test_reversed_nginx_access_log
215
+ def test_reversed_parse
162
216
  accesses = reversed_parse(join_lines(usual_log_line,
163
217
  usual_log_line,
164
218
  not_found_log_line))
@@ -168,8 +222,8 @@ module NginxAccessLogParserTests
168
222
  accesses)
169
223
  end
170
224
 
171
- def test_bad_nginx_access_log
172
- assert_raise(Racknga::NginxAccessLogParser::FormatError) do
225
+ def test_bad_log
226
+ assert_raise(Racknga::AccessLogParser::FormatError) do
173
227
  parse(join_lines(bad_log_line))
174
228
  end
175
229
  end
@@ -189,6 +243,12 @@ module NginxAccessLogParserTests
189
243
  include Data
190
244
  include Tests
191
245
 
246
+ private
247
+ def time_log_component
248
+ times = [time_local, runtime, request_time].compact
249
+ "[#{times.join(', ')}]"
250
+ end
251
+
192
252
  def runtime
193
253
  nil
194
254
  end
@@ -198,11 +258,40 @@ module NginxAccessLogParserTests
198
258
  end
199
259
  end
200
260
 
201
- class CombinedWithTimeLogTest < Test::Unit::TestCase
261
+ class CombinedWithTimeNginxLogTest < Test::Unit::TestCase
262
+ include Environment
263
+ include Data
264
+ include Tests
265
+
266
+ private
267
+ def time_log_component
268
+ times = [time_local, runtime, request_time].compact
269
+ "[#{times.join(', ')}]"
270
+ end
271
+
272
+ def runtime
273
+ 0.000573
274
+ end
275
+
276
+ def request_time
277
+ 0.001
278
+ end
279
+ end
280
+
281
+ class CombinedWithTimeApacheLogTest < Test::Unit::TestCase
202
282
  include Environment
203
283
  include Data
204
284
  include Tests
205
285
 
286
+ private
287
+ def time_log_component
288
+ "[#{time_local}]"
289
+ end
290
+
291
+ def log_line(content)
292
+ "#{content} #{runtime} #{(request_time * 1_000_000).to_i}"
293
+ end
294
+
206
295
  def runtime
207
296
  0.000573
208
297
  end
@@ -18,39 +18,35 @@ class InstanceNameTest < Test::Unit::TestCase
18
18
  include RackngaTestUtils
19
19
 
20
20
  def test_no_option
21
- server = default_instance_name.server
22
- user = default_instance_name.user
23
- revision = default_instance_name.revision
21
+ footer_variables = extract_from_default_instance_name
24
22
 
25
23
  instance_name_options({}) do
26
24
  request
27
- assert_header("Proc (at #{revision}) on #{server} by #{user}")
25
+ assert_header("Proc #{default_footer(*footer_variables)}")
28
26
  end
29
27
  end
30
28
 
31
29
  def test_application_name
32
30
  application_name = "HelloWorld"
33
- server = default_instance_name.server
34
- user = default_instance_name.user
35
- revision = default_instance_name.revision
31
+ footer_variables = extract_from_default_instance_name
36
32
 
37
33
  instance_name_options(:application_name => application_name) do
38
34
  request
39
- assert_header("#{application_name} (at #{revision}) on #{server} by #{user}")
35
+ assert_header("#{application_name} " +
36
+ "#{default_footer(*footer_variables)}")
40
37
  end
41
38
  end
42
39
 
43
40
  def test_both_application_name_and_version
44
41
  application_name = "HelloWorld"
45
42
  version = 1
46
- server = default_instance_name.server
47
- user = default_instance_name.user
48
- revision = default_instance_name.revision
43
+ footer_variables = extract_from_default_instance_name
49
44
 
50
45
  instance_name_options(:application_name => application_name,
51
46
  :version => version) do
52
47
  request
53
- assert_header("#{application_name} v#{version} (at #{revision}) on #{server} by #{user}")
48
+ assert_header("#{application_name} v#{version} " +
49
+ "#{default_footer(*footer_variables)}")
54
50
  end
55
51
  end
56
52
 
@@ -72,7 +68,22 @@ class InstanceNameTest < Test::Unit::TestCase
72
68
  end
73
69
 
74
70
  def default_instance_name
75
- @default_instance_name ||= create_instance_name(create_minimal_application).freeze
71
+ @default_instance_name ||=
72
+ create_instance_name(create_minimal_application).freeze
73
+ end
74
+
75
+ def extract_from_default_instance_name
76
+ server = default_instance_name.server
77
+ user = default_instance_name.user
78
+ revision = default_instance_name.revision
79
+ branch = default_instance_name.branch
80
+ ruby = default_instance_name.ruby
81
+
82
+ [server, user, revision, branch, ruby]
83
+ end
84
+
85
+ def default_footer(server, user, revision, branch, ruby)
86
+ "(at #{revision} (#{branch})) on #{server} by #{user} with #{ruby}"
76
87
  end
77
88
 
78
89
  def create_minimal_application
@@ -93,6 +104,7 @@ class InstanceNameTest < Test::Unit::TestCase
93
104
  end
94
105
 
95
106
  def assert_header(expected_header)
96
- assert_equal(expected_header, page.response_headers["X-Responsed-By"])
107
+ actual_header = page.response_headers["X-Responsed-By"]
108
+ assert_equal(expected_header, actual_header)
97
109
  end
98
110
  end
@@ -0,0 +1,70 @@
1
+ # Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
+
17
+ class NginxRawURITest < Test::Unit::TestCase
18
+ include RackngaTestUtils
19
+
20
+ class PseudoNginx
21
+ def initialize(application)
22
+ @application = application
23
+ end
24
+
25
+ def call(environment)
26
+ mimic_nginx_behavior(environment)
27
+
28
+ @application.call(environment)
29
+ end
30
+
31
+ private
32
+ def mimic_nginx_behavior(environment)
33
+ environment["HTTP_X_RAW_REQUEST_URI"] = environment["PATH_INFO"]
34
+ environment["PATH_INFO"] = Rack::Utils.unescape(environment["PATH_INFO"])
35
+ end
36
+ end
37
+
38
+ def app
39
+ application = Proc.new do |environment|
40
+ @environment = environment
41
+
42
+ response
43
+ end
44
+
45
+ raw_uri = Racknga::Middleware::NginxRawURI.new(application)
46
+ pseudo_nginx = PseudoNginx.new(raw_uri)
47
+
48
+ pseudo_nginx
49
+ end
50
+
51
+ def setup
52
+ Capybara.app = app
53
+ end
54
+
55
+ def test_slash
56
+ path_info_with_slash = "/keywords/GNU%2fLinux"
57
+
58
+ visit(path_info_with_slash)
59
+ assert_equal(path_info_with_slash, path_info)
60
+ end
61
+
62
+ private
63
+ def response
64
+ [200, {"Content-Type" => "plain/text"}, ["this is a response."]]
65
+ end
66
+
67
+ def path_info
68
+ @environment["PATH_INFO"]
69
+ end
70
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racknga
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- version: 0.9.2
9
+ - 3
10
+ version: 0.9.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kouhei Sutou
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-06 00:00:00 Z
18
+ date: 2011-11-12 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -145,17 +145,19 @@ files:
145
145
  - Rakefile
146
146
  - doc/text//news.textile
147
147
  - lib/racknga.rb
148
+ - lib/racknga/access_log_parser.rb
148
149
  - lib/racknga/cache_database.rb
149
150
  - lib/racknga/exception_mail_notifier.rb
150
151
  - lib/racknga/log_database.rb
152
+ - lib/racknga/log_entry.rb
151
153
  - lib/racknga/middleware/cache.rb
152
154
  - lib/racknga/middleware/deflater.rb
153
155
  - lib/racknga/middleware/exception_notifier.rb
154
156
  - lib/racknga/middleware/instance_name.rb
155
157
  - lib/racknga/middleware/jsonp.rb
156
158
  - lib/racknga/middleware/log.rb
159
+ - lib/racknga/middleware/nginx_raw_uri.rb
157
160
  - lib/racknga/middleware/range.rb
158
- - lib/racknga/nginx_access_log_parser.rb
159
161
  - lib/racknga/reverse_line_reader.rb
160
162
  - lib/racknga/utils.rb
161
163
  - lib/racknga/version.rb
@@ -166,10 +168,11 @@ files:
166
168
  - test/test-middleware-jsonp.rb
167
169
  - test/racknga-test-utils.rb
168
170
  - test/test-middleware-range.rb
171
+ - test/test-middleware-nginx-raw-uri.rb
169
172
  - test/run-test.rb
170
173
  - test/test-middleware-instance-name.rb
171
174
  - test/test-middleware-cache.rb
172
- - test/test-nginx-log-parser.rb
175
+ - test/test-access-log-parser.rb
173
176
  homepage: http://groonga.rubyforge.org/
174
177
  licenses:
175
178
  - LGPLv2.1 or later
@@ -199,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
202
  requirements: []
200
203
 
201
204
  rubyforge_project: groonga
202
- rubygems_version: 1.7.2
205
+ rubygems_version: 1.8.10
203
206
  signing_key:
204
207
  specification_version: 3
205
208
  summary: A Rack middleware collection for rroonga features.
@@ -207,7 +210,8 @@ test_files:
207
210
  - test/test-middleware-jsonp.rb
208
211
  - test/racknga-test-utils.rb
209
212
  - test/test-middleware-range.rb
213
+ - test/test-middleware-nginx-raw-uri.rb
210
214
  - test/run-test.rb
211
215
  - test/test-middleware-instance-name.rb
212
216
  - test/test-middleware-cache.rb
213
- - test/test-nginx-log-parser.rb
217
+ - test/test-access-log-parser.rb
@@ -1,139 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2011 Ryo Onodera <onodera@clear-code.com>
4
- #
5
- # This library is free software; you can redistribute it and/or
6
- # modify it under the terms of the GNU Lesser General Public
7
- # License as published by the Free Software Foundation; either
8
- # version 2.1 of the License, or (at your option) any later version.
9
- #
10
- # This library is distributed in the hope that it will be useful,
11
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
- # Lesser General Public License for more details.
14
- #
15
- # You should have received a copy of the GNU Lesser General Public
16
- # License along with this library; if not, write to the Free Software
17
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
-
19
- require "racknga/reverse_line_reader"
20
-
21
- module Racknga
22
- # Supported formats:
23
- # * combined (default format)
24
- # * combined_with_time (custom format for Passenger)
25
- #
26
- # Configurations in nginx:
27
- # * combined
28
- # log_format combined '$remote_addr - $remote_user [$time_local] '
29
- # '"$request" $status $body_bytes_sent '
30
- # '"$http_referer" "$http_user_agent"';
31
- # access_log log/access.log combined
32
- #
33
- # * combined_with_time
34
- # log_format combined_with_time '$remote_addr - $remote_user '
35
- # '[$time_local, $upstream_http_x_runtime, $request_time] '
36
- # '"$request" $status $body_bytes_sent '
37
- # '"$http_referer" "$http_user_agent"';
38
- # access_log log/access.log combined_with_time
39
- class NginxAccessLogParser
40
- include Enumerable
41
-
42
- class FormatError < StandardError
43
- end
44
-
45
- def initialize(line_reader)
46
- @line_reader = line_reader
47
- end
48
-
49
- def each
50
- @line_reader.each do |line|
51
- line.force_encoding("UTF-8")
52
- yield(parse_line(line)) if line.valid_encoding?
53
- end
54
- end
55
-
56
- REMOTE_ADDRESS = '(?:\d{1,3}\.){3}\d{1,3}'
57
- REMOTE_USER = '[^ ]+'
58
- TIME_LOCAL = '[^ ]+ \+\d{4}'
59
- RUNTIME = '(?:[\d.]+|-)'
60
- REQUEST_TIME = '[\d.]+'
61
- REQUEST = '.*?'
62
- STATUS = '\d{3}'
63
- BODY_BYTES_SENT = '\d+'
64
- HTTP_REFERER = '.*?'
65
- HTTP_USER_AGENT = '(?:\\"|[^\"])*?' # '
66
- LOG_FORMAT =
67
- /\A(#{REMOTE_ADDRESS}) - (#{REMOTE_USER}) \[(#{TIME_LOCAL})(?:, (#{RUNTIME}), (#{REQUEST_TIME}))?\] "(#{REQUEST})" (#{STATUS}) (#{BODY_BYTES_SENT}) "(#{HTTP_REFERER})" "(#{HTTP_USER_AGENT})"\n\z/
68
- def parse_line(line)
69
- if line =~ LOG_FORMAT
70
- last_match = Regexp.last_match
71
- options = {}
72
- options[:remote_address] = last_match[1]
73
- options[:remote_user] = last_match[2]
74
- parse_time_local(last_match[3], options)
75
- options[:runtime] = last_match[4].to_f
76
- options[:request_time] = last_match[5].to_f
77
- options[:request] = last_match[6]
78
- options[:status] = last_match[7].to_i
79
- options[:body_bytes_sent] = last_match[8].to_i
80
- options[:http_referer] = last_match[9]
81
- options[:http_user_agent] = last_match[10]
82
- LogEntry.new(options)
83
- else
84
- raise FormatError.new("ill-formatted log entry: #{line.inspect} !~ #{LOG_FORMAT}")
85
- end
86
- end
87
-
88
- def parse_time_local(token, options)
89
- day, month, year, hour, minute, second, _time_zone = token.split(/[\/: ]/)
90
- options[:time_local] = Time.local(year, month, day, hour, minute, second)
91
- end
92
- end
93
-
94
- class ReversedNginxAccessLogParser < NginxAccessLogParser
95
- def initialize(line_reader)
96
- @line_reader = ReverseLineReader.new(line_reader)
97
- end
98
- end
99
-
100
- class LogEntry
101
- ATTRIBUTES = [
102
- :remote_address,
103
- :remote_user,
104
- :time_local,
105
- :runtime,
106
- :request_time,
107
- :request,
108
- :status,
109
- :body_bytes_sent,
110
- :http_referer,
111
- :http_user_agent,
112
- ]
113
-
114
- attr_reader(*ATTRIBUTES)
115
- def initialize(options=nil)
116
- options ||= {}
117
- @remote_address = options[:remote_address]
118
- @remote_user = options[:remote_user]
119
- @time_local = options[:time_local] || Time.at(0)
120
- @runtime = options[:runtime] || 0.0
121
- @request_time = options[:request_time] || 0.0
122
- @request = options[:request]
123
- @status = options[:status]
124
- @body_bytes_sent = options[:body_bytes_sent]
125
- @http_referer = options[:http_referer]
126
- @http_user_agent = options[:http_user_agent]
127
- end
128
-
129
- def attributes
130
- ATTRIBUTES.collect do |attribute|
131
- __send__(attribute)
132
- end
133
- end
134
-
135
- def ==(other)
136
- attributes == other.attributes
137
- end
138
- end
139
- end