racknga 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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