racknga 0.9.3 → 0.9.5

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -12
  3. data/README.md +44 -0
  4. data/Rakefile +19 -272
  5. data/doc/text/{news.textile → news.md} +24 -7
  6. data/lib/racknga/access_log_parser.rb +1 -1
  7. data/lib/racknga/api-keys.rb +39 -0
  8. data/lib/racknga/cache_database.rb +2 -2
  9. data/lib/racknga/exception_mail_notifier.rb +18 -17
  10. data/lib/racknga/log_database.rb +1 -1
  11. data/lib/racknga/log_entry.rb +1 -1
  12. data/lib/racknga/middleware/auth/api-key.rb +95 -0
  13. data/lib/racknga/middleware/cache.rb +23 -11
  14. data/lib/racknga/middleware/deflater.rb +1 -1
  15. data/lib/racknga/middleware/exception_notifier.rb +1 -1
  16. data/lib/racknga/middleware/instance_name.rb +65 -12
  17. data/lib/racknga/middleware/jsonp.rb +26 -9
  18. data/lib/racknga/middleware/log.rb +1 -1
  19. data/lib/racknga/middleware/nginx_raw_uri.rb +1 -1
  20. data/lib/racknga/middleware/range.rb +4 -6
  21. data/lib/racknga/reverse_line_reader.rb +1 -1
  22. data/lib/racknga/utils.rb +1 -1
  23. data/lib/racknga/version.rb +3 -5
  24. data/lib/racknga.rb +2 -1
  25. data/munin/plugins/passenger_application_ +61 -47
  26. data/munin/plugins/passenger_status +64 -33
  27. data/test/racknga-test-utils.rb +2 -6
  28. data/test/run-test.rb +2 -5
  29. data/test/test-access-log-parser.rb +1 -1
  30. data/test/test-api-keys.rb +80 -0
  31. data/test/test-middleware-auth-api-key.rb +144 -0
  32. data/test/test-middleware-cache.rb +1 -1
  33. data/test/test-middleware-instance-name.rb +32 -10
  34. data/test/test-middleware-jsonp.rb +14 -2
  35. data/test/test-middleware-nginx-raw-uri.rb +1 -1
  36. data/test/test-middleware-range.rb +1 -1
  37. metadata +182 -157
  38. data/README.textile +0 -52
  39. data/lib/racknga/will_paginate.rb +0 -48
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU General Public License as published by
@@ -23,16 +23,11 @@ require 'rubygems'
23
23
 
24
24
  mode = ARGV[0]
25
25
 
26
- def passenger_status_path(gem_path)
27
- File.join(gem_path, "bin", "passenger-status")
28
- end
29
-
30
26
  @label = ENV["label"]
31
27
  @pid_file = ENV["pid_file"]
32
28
  @ruby = ENV["ruby"] || Gem.ruby
33
- @gem_path = ((ENV["GEM_HOME"] || '').split(/:/) + Gem.path).find do |path|
34
- File.exist?(passenger_status_path(path))
35
- end
29
+ passenger_spec = Gem::Specification.find_by_name("passenger")
30
+ @passenger_status = passenger_spec.bin_file("passenger-status")
36
31
 
37
32
  if @label
38
33
  parameter_prefix = /\Apassenger_(?:#{@label}_)?application_/
@@ -40,7 +35,7 @@ else
40
35
  parameter_prefix = /\Apassenger_application_/
41
36
  end
42
37
  parameter = File.basename($0).gsub(parameter_prefix, '')
43
- if /_(sessions|processed|uptime)\z/ =~ parameter
38
+ if /_(\S+)\z/ =~ parameter
44
39
  @application = $PREMATCH
45
40
  @type = $1
46
41
  @application = nil if @application and @application.empty?
@@ -57,28 +52,25 @@ def passenger_status
57
52
  else
58
53
  pid = nil
59
54
  end
60
- result = `#{@ruby} #{passenger_status_path(@gem_path)} #{pid}`
55
+ result = `#{@ruby} #{@passenger_status} #{pid}`
61
56
  [$?.success?, result]
62
57
  end
63
58
 
64
- def parse_uptime(uptime)
65
- uptime_in_minutes = 0
66
- if /\A(?:(?:(\d+)h\s+)?(\d+)m\s+)?(\d+)s\z/ =~ uptime
59
+ def parse_time(time)
60
+ time_in_minutes = 0
61
+ if /\A(?:(?:(\d+)h\s+)?(\d+)m\s+)?(\d+)s\z/ =~ time
67
62
  hours = $1.to_i
68
63
  minutes = $2.to_i
69
64
  seconds = $3.to_i
70
- uptime_in_minutes = minutes + hours * 60
65
+ time_in_minutes = minutes + hours * 60
71
66
  end
72
- uptime_in_minutes
67
+ time_in_minutes
73
68
  end
74
69
 
75
70
  def extract_application_name(path)
76
71
  components = path.split(/\//)
77
- ignore_components = ["current"]
78
- while ignore_components.include?(components.last)
79
- components.pop
80
- end
81
- components.pop
72
+ application_name, tag = components.last.split(/#/)
73
+ application_name
82
74
  end
83
75
 
84
76
  def parse_result(result)
@@ -86,34 +78,47 @@ def parse_result(result)
86
78
  section = nil
87
79
  application_name = nil
88
80
  result.each_line do |line|
89
- case section
90
- when "Application groups"
91
- case line.chomp
92
- when /\A(.+):\s*\z/
93
- path = $1
94
- application_name = extract_application_name(path)
95
- sections[section] << [application_name, []]
96
- when /\A\s+\*\s+
97
- PID:\s+(\d+)\s+
98
- Sessions:\s+(\d+)\s+
99
- Processed:\s+(\d+)\s+
100
- Uptime:\s+(.+)\z/x
101
- pid = $1.to_i
102
- sessions = $2.to_i
103
- processed = $3.to_i
104
- uptime = parse_uptime($4)
105
- _application_name, processes = sections[section].last
106
- processes << {
107
- :pid => pid,
108
- :sessions => sessions,
109
- :processed => processed,
110
- :uptime => uptime
111
- }
112
- end
81
+ case line
82
+ when /-+\s+(.+)\s+-+/
83
+ section = $1
84
+ sections[section] = []
113
85
  else
114
- if /-+\s+(.+)\s+-+/ =~ line
115
- section = $1
116
- sections[section] = []
86
+ case section
87
+ when "Application groups"
88
+ case line.chomp
89
+ when /\A(\/.+):\s*\z/
90
+ path = $1
91
+ application_name = extract_application_name(path)
92
+ sections[section] << [application_name, []]
93
+ when /\A\s+\*\s+
94
+ PID:\s+(\d+)\s+
95
+ Sessions:\s+(\d+)\s+
96
+ Processed:\s+(\d+)\s+
97
+ Uptime:\s+(.+)\z/x
98
+ pid = $1.to_i
99
+ sessions = $2.to_i
100
+ processed = $3.to_i
101
+ uptime = parse_time($4)
102
+ _application_name, processes = sections[section].last
103
+ processes << {
104
+ :pid => pid,
105
+ :sessions => sessions,
106
+ :processed => processed,
107
+ :uptime => uptime
108
+ }
109
+ when /\A\s+
110
+ CPU:\s+(\d+)%\s+
111
+ Memory\s*:\s+(\d+)M\s+
112
+ Last\sused:\s+(.+)(?:\s+ago)\z/x
113
+ cpu = $1.to_i
114
+ memory = $2.to_i * 1024 * 1024
115
+ last_used = parse_time($3)
116
+ _application_name, processes = sections[section].last
117
+ process = processes.last
118
+ process[:cpu] = cpu
119
+ process[:memory] = memory
120
+ process[:last_used] = last_used
121
+ end
117
122
  end
118
123
  end
119
124
  end
@@ -138,6 +143,12 @@ def vlabel
138
143
  "number of processed sessions"
139
144
  when "uptime"
140
145
  "uptime by minutes"
146
+ when "cpu"
147
+ "CPU usage in percent"
148
+ when "memory"
149
+ "memory usage"
150
+ when "last_used"
151
+ "last used by minutes"
141
152
  else
142
153
  "unknown"
143
154
  end
@@ -219,6 +230,9 @@ when "suggest"
219
230
  puts "#{application}_sessions"
220
231
  puts "#{application}_processed"
221
232
  puts "#{application}_uptime"
233
+ puts "#{application}_cpu"
234
+ puts "#{application}_memory"
235
+ puts "#{application}_last_used"
222
236
  end
223
237
  exit(true)
224
238
  end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU General Public License as published by
@@ -22,16 +22,11 @@ require 'rubygems'
22
22
 
23
23
  mode = ARGV[0]
24
24
 
25
- def passenger_status_path(gem_path)
26
- File.join(gem_path, "bin", "passenger-status")
27
- end
28
-
29
25
  @label = ENV["label"]
30
26
  @pid_file = ENV["pid_file"]
31
27
  @ruby = ENV["ruby"] || Gem.ruby
32
- @gem_path = ((ENV["GEM_HOME"] || '').split(/:/) + Gem.path).find do |path|
33
- File.exist?(passenger_status_path(path))
34
- end
28
+ passenger_spec = Gem::Specification.find_by_name("passenger")
29
+ @passenger_status = passenger_spec.bin_file("passenger-status")
35
30
 
36
31
  def passenger_status
37
32
  if @pid_file
@@ -42,32 +37,60 @@ def passenger_status
42
37
  else
43
38
  pid = nil
44
39
  end
45
- result = `#{@ruby} #{passenger_status_path(@gem_path)} #{pid}`
40
+ result = `#{@ruby} #{@passenger_status} #{pid}`
46
41
  [$?.success?, result]
47
42
  end
48
43
 
44
+ def label_to_key(label)
45
+ label.downcase.gsub(/[ -]+/, "_")
46
+ end
47
+
48
+ def extract_application_name(path)
49
+ components = path.split(/\//)
50
+ application_name, tag = components.last.split(/#/)
51
+ application_name
52
+ end
53
+
49
54
  def parse_result(result)
50
55
  sections = {}
51
56
  section = nil
52
57
  result.each_line do |line|
53
- case section
54
- when "General information"
55
- case line.chomp
56
- when /\A(\S+)\s*=\s*(\d+)\z/
57
- key = $1
58
- value = $2.to_i
59
- sections[section] << {:key => key, :label => key, :value => value}
60
- when /\A(Waiting on global queue):\s*(\d+)\z/
61
- label = $1
62
- value = $2.to_i
63
- sections[section] << {:key => "global_queue",
64
- :label => label,
65
- :value => value}
66
- end
58
+ case line
59
+ when /-+\s+(.+)\s+-+/
60
+ section = $1
61
+ sections[section] = []
67
62
  else
68
- if /-+\s+(.+)\s+-+/ =~ line
69
- section = $1
70
- sections[section] = []
63
+ case section
64
+ when "General information"
65
+ case line.chomp
66
+ when /\A(.+):\s*(\d+)\z/
67
+ label = $1.strip
68
+ value = $2.to_i
69
+ key = label_to_key(label)
70
+ sections[section] << {
71
+ :key => key,
72
+ :label => label,
73
+ :value => value,
74
+ }
75
+ end
76
+ when "Application groups"
77
+ case line.chomp
78
+ when /\A(\/.+):\s*\z/
79
+ path = $1
80
+ application_name = extract_application_name(path)
81
+ sections[section] << [application_name, []]
82
+ when /\A\s+(.+):\s*(\d+)\z/
83
+ label = $1.strip
84
+ value = $2.to_i
85
+ _application_name, attributes = sections[section].last
86
+ key = "#{_application_name}_#{label_to_key(label)}"
87
+ label = "#{label}: #{_application_name}"
88
+ attributes << {
89
+ :key => key,
90
+ :label => label,
91
+ :value => value,
92
+ }
93
+ end
71
94
  end
72
95
  end
73
96
  end
@@ -95,12 +118,16 @@ graph_vlabel number of processes
95
118
 
96
119
  EOC
97
120
  have_stack_base = false
98
- sections["General information"].each do |attributes|
99
- key = attributes[:key]
121
+ attributes = sections["General information"]
122
+ sections["Application groups"].each do |_, application_attributes|
123
+ attributes += application_attributes
124
+ end
125
+ attributes.each do |attribute|
126
+ key = attribute[:key]
100
127
  next if key == "count"
101
- puts("#{key}.label #{attributes[:label]}")
128
+ puts("#{key}.label #{attribute[:label]}")
102
129
  case key
103
- when "max", "global_queue"
130
+ when /max/, /queue/
104
131
  draw = "LINE2"
105
132
  else
106
133
  if have_stack_base
@@ -110,7 +137,7 @@ EOC
110
137
  have_stack_base = true
111
138
  end
112
139
  end
113
- puts("#{attributes[:key]}.draw #{draw}")
140
+ puts("#{attribute[:key]}.draw #{draw}")
114
141
  end
115
142
  end
116
143
 
@@ -119,8 +146,12 @@ def report
119
146
  exit(false) unless success
120
147
 
121
148
  sections = parse_result(result)
122
- sections["General information"].each do |attributes|
123
- puts("#{attributes[:key]}.value #{attributes[:value]}")
149
+ attributes = sections["General information"]
150
+ sections["Application groups"].each do |_, application_attributes|
151
+ attributes += application_attributes
152
+ end
153
+ attributes.each do |attribute|
154
+ puts("#{attribute[:key]}.value #{attribute[:value]}")
124
155
  end
125
156
  end
126
157
 
@@ -12,20 +12,16 @@
12
12
  #
13
13
  # You should have received a copy of the GNU Lesser General Public
14
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
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
  require "test/unit/capybara"
18
18
  require 'json'
19
19
 
20
20
  require 'racknga'
21
+ require 'racknga/middleware/auth/api-key'
21
22
  require 'racknga/middleware/cache'
22
23
  require 'racknga/middleware/instance_name'
23
24
 
24
- Capybara.configure do |config|
25
- config.default_driver = nil
26
- config.current_driver = nil
27
- end
28
-
29
25
  module RackngaTestUtils
30
26
  include Capybara::DSL
31
27
 
data/test/run-test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (C) 2010-2011 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2010-2024 Sutou Kouhei <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
@@ -14,12 +14,10 @@
14
14
  #
15
15
  # You should have received a copy of the GNU Lesser General Public
16
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
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
19
  $VERBOSE = true
20
20
 
21
- $KCODE = "u" if RUBY_VERSION < "1.9"
22
-
23
21
  require 'pathname'
24
22
 
25
23
  base_dir = Pathname(__FILE__).dirname.parent.expand_path
@@ -33,7 +31,6 @@ require "rubygems"
33
31
  require "bundler/setup"
34
32
 
35
33
  require 'test/unit'
36
- require 'test/unit/notify'
37
34
 
38
35
  Test::Unit::Priority.enable
39
36
 
@@ -15,7 +15,7 @@
15
15
  #
16
16
  # You should have received a copy of the GNU Lesser General Public
17
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
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
 
20
20
  require 'stringio'
21
21
 
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Haruka Yoshihara <yoshihara@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ class APIKeysTest < Test::Unit::TestCase
20
+ include RackngaTestUtils
21
+
22
+ def setup
23
+ @api_keys = Racknga::APIKeys.new(query_parameter, api_keys)
24
+ end
25
+
26
+ def test_matched_key
27
+ assert_matched(query_parameter, api_key)
28
+ end
29
+
30
+ def test_not_matched_key
31
+ assert_not_matched(query_parameter, "notapikey")
32
+ end
33
+
34
+ def test_empty_key
35
+ assert_not_matched(query_parameter, "")
36
+ end
37
+
38
+ def test_empty_query_parameter
39
+ assert_not_matched("", api_key)
40
+ end
41
+
42
+ def test_mismatched_query_parameter
43
+ assert_not_matched("not-api-key", api_key)
44
+ end
45
+
46
+ private
47
+ def assert_matched(parameter, key)
48
+ environment = generate_environment(parameter, key)
49
+ assert_true(@api_keys.include?(environment))
50
+ end
51
+
52
+ def assert_not_matched(parameter, key)
53
+ environment = generate_environment(parameter, key)
54
+ assert_false(@api_keys.include?(environment))
55
+ end
56
+
57
+ def generate_environment(parameter, key)
58
+ url = "http://example.org"
59
+
60
+ unless parameter.empty?
61
+ api_url = "#{url}?#{parameter}=#{key}"
62
+ else
63
+ api_url = url
64
+ end
65
+
66
+ Rack::MockRequest.env_for(api_url)
67
+ end
68
+
69
+ def query_parameter
70
+ "api-key"
71
+ end
72
+
73
+ def api_keys
74
+ ["key", "key2"]
75
+ end
76
+
77
+ def api_key
78
+ api_keys.first
79
+ end
80
+ end
@@ -0,0 +1,144 @@
1
+ # Copyright (C) 2012 Haruka Yoshihara <kou@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ class MiddlewareAuthAPIKeyTest < Test::Unit::TestCase
18
+ include RackngaTestUtils
19
+
20
+ def app
21
+ application = Proc.new do |environment|
22
+ @environment = environment
23
+ [
24
+ 200,
25
+ {"Content-Type" => "application/json"},
26
+ [@body]
27
+ ]
28
+ end
29
+ authorized_api_keys =
30
+ Racknga::APIKeys.new(@api_query_parameter, @api_keys)
31
+ api_key_options = {
32
+ :api_url_prefixes => [@api_url_prefix],
33
+ :authorized_api_keys => authorized_api_keys,
34
+ :error_response => @error_response,
35
+ :disable_authorization => disable_authorization?
36
+ }
37
+ api_key = api_key_authorizer(application, api_key_options)
38
+ Proc.new do |environment|
39
+ api_key.call(environment)
40
+ end
41
+ end
42
+
43
+ def setup
44
+ @api_query_parameter = "api-key"
45
+ @api_url_prefix = "/api"
46
+ @api_keys = ["key1", "key2"]
47
+ @body = "{}"
48
+ @error_response = {"error" => "this api key is not authorized"}
49
+ Capybara.app = app
50
+ end
51
+
52
+ def test_authorized_key
53
+ url = generate_url(url_prefix, query_parameter, valid_key)
54
+ visit(url)
55
+ assert_success_response
56
+ end
57
+
58
+ def test_unauthorized_key
59
+ url = generate_url(url_prefix, query_parameter, "invalidkey")
60
+ visit(url)
61
+ assert_failure_response
62
+ end
63
+
64
+ def test_unmatched_query_parameter
65
+ url = generate_url(url_prefix, "not-api-key", valid_key)
66
+ visit(url)
67
+ assert_failure_response
68
+ end
69
+
70
+ def test_unauthorized_key_and_unmatched_query_parameter
71
+ url = generate_url(url_prefix, "not-api-key", "invalidkey")
72
+ visit(url)
73
+ assert_failure_response
74
+ end
75
+
76
+ def test_not_api_url
77
+ visit("/not/api/url")
78
+ assert_success_response
79
+ end
80
+
81
+ private
82
+ def disable_authorization?
83
+ false
84
+ end
85
+
86
+ def api_key_authorizer(application, api_key_options)
87
+ Racknga::Middleware::Auth::APIKey.new(application, api_key_options)
88
+ end
89
+
90
+ def generate_url(path, query_parameter, api_key)
91
+ url = path
92
+ if query_parameter
93
+ url << "?#{query_parameter}=#{api_key}"
94
+ end
95
+ url
96
+ end
97
+
98
+ def url_prefix
99
+ @api_url_prefix
100
+ end
101
+
102
+ def query_parameter
103
+ @api_query_parameter
104
+ end
105
+
106
+ def valid_key
107
+ @api_keys.first
108
+ end
109
+
110
+ def assert_success_response
111
+ assert_equal(200, page.status_code)
112
+ assert_equal(@body, page.source)
113
+ end
114
+
115
+ def assert_failure_response
116
+ assert_equal(401, page.status_code)
117
+ assert_equal(@error_response.to_json, page.source)
118
+ end
119
+
120
+ class DisableAuthorizationTest < self
121
+ def test_unauthorized_key
122
+ url = generate_url(url_prefix, query_parameter, "invalidkey")
123
+ visit(url)
124
+ assert_success_response
125
+ end
126
+
127
+ def test_unmatched_query_parameter
128
+ url = generate_url(url_prefix, "not-api-key", valid_key)
129
+ visit(url)
130
+ assert_success_response
131
+ end
132
+
133
+ def test_unauthorized_key_and_unmatched_query_parameter
134
+ url = generate_url(url_prefix, "not-api-key", "invalidkey")
135
+ visit(url)
136
+ assert_success_response
137
+ end
138
+
139
+ private
140
+ def disable_authorization?
141
+ true
142
+ end
143
+ end
144
+ end
@@ -12,7 +12,7 @@
12
12
  #
13
13
  # You should have received a copy of the GNU Lesser General Public
14
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
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
  class MiddlewareCacheTest < Test::Unit::TestCase
18
18
  include RackngaTestUtils