racknga 0.9.0 → 0.9.1

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.
@@ -0,0 +1,94 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2010 Kouhei Sutou <kou@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/log_database'
20
+
21
+ module Racknga
22
+ module Middleware
23
+ class Log
24
+ LOGGER_KEY = "racknga.logger"
25
+
26
+ def initialize(application, options={})
27
+ @application = application
28
+ @options = Utils.normalize_options(options || {})
29
+ database_path = @options[:database_path]
30
+ raise ArgumentError, ":database_path is missing" if database_path.nil?
31
+ @database = LogDatabase.new(database_path)
32
+ @logger = Logger.new(@database)
33
+ end
34
+
35
+ def call(environment)
36
+ environment[LOGGER_KEY] = @logger
37
+
38
+ start_time = Time.now
39
+ status, headers, body = @application.call(environment)
40
+ end_time = Time.now
41
+
42
+ request = Rack::Request.new(environment)
43
+ log(start_time, end_time, request, status, headers, body)
44
+
45
+ [status, headers, body]
46
+ end
47
+
48
+ def ensure_database
49
+ @database.ensure_database
50
+ end
51
+
52
+ def close_database
53
+ @database.close_database
54
+ end
55
+
56
+ private
57
+ def log(start_time, end_time, request, status, headers, body)
58
+ request_time = end_time - start_time
59
+ length = headers["Content-Length"] || "-"
60
+ length = "-" if length == "0"
61
+ format = "%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\" %0.8f"
62
+ message = format % [request.ip || "-",
63
+ request.env["REMOTE_USER"] || "-",
64
+ end_time.dup.utc.strftime("%d/%b/%Y:%H:%M:%S %z"),
65
+ request.request_method,
66
+ request.fullpath,
67
+ request.env["SERVER_PROTOCOL"] || "-",
68
+ status.to_s[0..3],
69
+ length,
70
+ request.env["HTTP_REFERER"] || "-",
71
+ request.user_agent || "-",
72
+ request_time]
73
+ @logger.log("access",
74
+ request.fullpath,
75
+ :message => message,
76
+ :user_agent => request.user_agent,
77
+ :runtime => request_time)
78
+ end
79
+
80
+ class Logger
81
+ def initialize(database)
82
+ @database = database
83
+ @entries = @database.entries
84
+ end
85
+
86
+ def log(tag, path, options={})
87
+ @entries.add(options.merge(:time_stamp => Time.now,
88
+ :tag => tag,
89
+ :path => path))
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,159 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2010 Kouhei Sutou <kou@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 'time'
20
+
21
+ module Racknga
22
+ module Middleware
23
+ class Range
24
+ def initialize(application)
25
+ @application = application
26
+ end
27
+
28
+ def call(environment)
29
+ status, headers, body = @application.call(environment)
30
+ return [status, headers, body] if status != 200
31
+
32
+ headers = Rack::Utils::HeaderHash.new(headers)
33
+ headers["Accept-Ranges"] = "bytes"
34
+ request = Rack::Request.new(environment)
35
+ range = request.env["HTTP_RANGE"]
36
+ if range and /\Abytes=(\d*)-(\d*)\z/ =~ range
37
+ first_byte, last_byte = $1, $2
38
+ status, headers, body = apply_range(status, headers, body, request,
39
+ first_byte, last_byte)
40
+ end
41
+ [status, headers.to_hash, body]
42
+ end
43
+
44
+ private
45
+ def apply_range(status, headers, body, request, first_byte, last_byte)
46
+ unless use_range?(request, headers)
47
+ return [status, headers, body]
48
+ end
49
+ length = guess_length(headers, body)
50
+ return [status, headers.to_hash, body] if length.nil?
51
+
52
+ if first_byte.empty? and last_byte.empty?
53
+ headers["Content-Length"] = "0"
54
+ return [Rack::Utils.status_code(:requested_range_not_satisfiable),
55
+ headers,
56
+ []]
57
+ end
58
+
59
+ if last_byte.empty?
60
+ last_byte = length - 1
61
+ else
62
+ last_byte = last_byte.to_i
63
+ end
64
+ if first_byte.empty?
65
+ first_byte = length - last_byte
66
+ last_byte = length - 1
67
+ else
68
+ first_byte = first_byte.to_i
69
+ end
70
+
71
+ byte_range_spec = "#{first_byte}-#{last_byte}/#{length}"
72
+ range_length = last_byte - first_byte + 1
73
+ headers["Content-Range"] = "bytes #{byte_range_spec}"
74
+ headers["Content-Length"] = range_length.to_s
75
+ stream = RangeStream.new(body, first_byte, range_length)
76
+ if body.respond_to?(:to_path)
77
+ def stream.to_path
78
+ @body.to_path
79
+ end
80
+ end
81
+ [Rack::Utils.status_code(:partial_content),
82
+ headers,
83
+ stream]
84
+ end
85
+
86
+ def use_range?(request, headers)
87
+ if_range = request.env["HTTP_IF_RANGE"]
88
+ return true if if_range.nil?
89
+
90
+ if /\A(?:Mo|Tu|We|Th|Fr|Sa|Su)/ =~ if_range
91
+ last_modified = headers["Last-Modified"]
92
+ return false if last_modified.nil?
93
+ begin
94
+ if_range = Time.httpdate(if_range)
95
+ last_modified = Time.httpdate(last_modified)
96
+ rescue ArgumentError
97
+ return true
98
+ end
99
+ if_range == last_modified
100
+ else
101
+ if_range == headers["ETag"]
102
+ end
103
+ end
104
+
105
+ def guess_length(headers, body)
106
+ length = headers["Content-Length"]
107
+ return length.to_i unless length.nil?
108
+ return body.stat.size if body.respond_to?(:stat)
109
+ nil
110
+ end
111
+
112
+ class RangeStream
113
+ def initialize(body, first_byte, length)
114
+ @body = body
115
+ @first_byte = first_byte
116
+ @length = length
117
+ end
118
+
119
+ def each
120
+ if @body.respond_to?(:seek)
121
+ @body.seek(@first_byte)
122
+ start = 0
123
+ else
124
+ start = @first_byte
125
+ end
126
+ rest = @length
127
+
128
+ @body.each do |chunk|
129
+ if chunk.respond_to?(:encoding)
130
+ if chunk.encoding != Encoding::ASCII_8BIT
131
+ chunk = chunk.dup.force_encoding(Encoding::ASCII_8BIT)
132
+ end
133
+ end
134
+
135
+ chunk_size = chunk.size
136
+ if start > 0
137
+ if chunk_size < start
138
+ start -= chunk_size
139
+ next
140
+ else
141
+ chunk = chunk[start..-1]
142
+ chunk_size -= start
143
+ start = 0
144
+ end
145
+ end
146
+ if rest > chunk_size
147
+ yield(chunk)
148
+ rest -= chunk_size
149
+ else
150
+ yield(chunk[0, rest])
151
+ rest -= rest
152
+ end
153
+ break if rest <= 0
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
data/lib/racknga/utils.rb CHANGED
@@ -4,7 +4,8 @@
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
7
- # License version 2.1 as published by the Free Software Foundation.
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
8
9
  #
9
10
  # This library is distributed in the hope that it will be useful,
10
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -4,7 +4,8 @@
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
7
- # License version 2.1 as published by the Free Software Foundation.
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
8
9
  #
9
10
  # This library is distributed in the hope that it will be useful,
10
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -16,5 +17,5 @@
16
17
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
 
18
19
  module Racknga
19
- VERSION = '0.9.0'
20
+ VERSION = '0.9.1'
20
21
  end
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2010 SHIMODA Hiroshi <shimoda@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 "groonga"
20
+
21
+ module Groonga
22
+ module WillPaginateAPI
23
+ def total_pages
24
+ n_pages
25
+ end
26
+
27
+ def per_page
28
+ n_records_in_page
29
+ end
30
+
31
+ def total_entries
32
+ n_records
33
+ end
34
+
35
+ def out_of_bounds?
36
+ current_page > total_pages
37
+ end
38
+
39
+ def offset
40
+ (start_offset || 0) - 1
41
+ end
42
+ end
43
+
44
+ module Pagination
45
+ include WillPaginateAPI
46
+ end
47
+ end
data/lib/racknga.rb CHANGED
@@ -4,7 +4,8 @@
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
7
- # License version 2.1 as published by the Free Software Foundation.
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
8
9
  #
9
10
  # This library is distributed in the hope that it will be useful,
10
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,11 +16,11 @@
15
16
  # License along with this library; if not, write to the Free Software
16
17
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
 
18
- require 'groonga'
19
19
  require 'rack'
20
20
 
21
21
  require 'racknga/version'
22
22
  require 'racknga/utils'
23
- require 'racknga/middleware/cache'
24
23
  require 'racknga/middleware/deflater'
25
24
  require 'racknga/middleware/exception_notifier'
25
+ require 'racknga/middleware/jsonp'
26
+ require 'racknga/middleware/range'
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program 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
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+ #%# family=auto
19
+ #%# capabilities=autoconf suggest
20
+
21
+ require 'English'
22
+ require 'rubygems'
23
+
24
+ mode = ARGV[0]
25
+
26
+ def passenger_status_path(gem_path)
27
+ File.join(gem_path, "bin", "passenger-status")
28
+ end
29
+
30
+ @label = ENV["label"]
31
+ @pid_file = ENV["pid_file"]
32
+ @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
36
+
37
+ if @label
38
+ parameter_prefix = /\Apassenger_(?:#{@label}_)?domain_/
39
+ else
40
+ parameter_prefix = /\Apassenger_domain_/
41
+ end
42
+ parameter = File.basename($0).gsub(parameter_prefix, '')
43
+ if /_(sessions|processed|uptime)\z/ =~ parameter
44
+ @domain = $PREMATCH
45
+ @type = $1
46
+ @domain = nil if @domain and @domain.empty?
47
+ else
48
+ @domain = @type = nil
49
+ end
50
+
51
+ def passenger_status
52
+ if @pid_file
53
+ unless File.readable?(@pid_file)
54
+ return [false, "PID file isn't readable: #{@pid_file}"]
55
+ end
56
+ pid = File.read(@pid_file).strip
57
+ else
58
+ pid = nil
59
+ end
60
+ result = `#{@ruby} #{passenger_status_path(@gem_path)} #{pid}`
61
+ [$?.success?, result]
62
+ end
63
+
64
+ def parse_uptime(uptime)
65
+ uptime_in_minutes = 0
66
+ if /\A(?:(?:(\d+)h\s+)?(\d+)m\s+)?(\d+)s\z/ =~ uptime
67
+ hours = $1.to_i
68
+ minutes = $2.to_i
69
+ seconds = $3.to_i
70
+ uptime_in_minutes = minutes + hours * 60
71
+ end
72
+ uptime_in_minutes
73
+ end
74
+
75
+ def extract_domain(path)
76
+ components = path.split(/\//)
77
+ ignore_components = ["current"]
78
+ while ignore_components.include?(components.last)
79
+ components.pop
80
+ end
81
+ components.pop
82
+ end
83
+
84
+ def parse_result(result)
85
+ sections = {}
86
+ section = nil
87
+ domain = nil
88
+ result.each_line do |line|
89
+ case section
90
+ when "Domains"
91
+ case line.chomp
92
+ when /\A(.+):\s*\z/
93
+ path = $1
94
+ domain = extract_domain(path)
95
+ sections[section] << [domain, []]
96
+ when /\A\s+PID:\s+(\d+)\s+
97
+ Sessions:\s+(\d+)\s+
98
+ Processed:\s+(\d+)\s+
99
+ Uptime:\s+(.+)\z/x
100
+ pid = $1.to_i
101
+ sessions = $2.to_i
102
+ processed = $3.to_i
103
+ uptime = parse_uptime($4)
104
+ _domain, processes = sections[section].last
105
+ processes << {
106
+ :pid => pid,
107
+ :sessions => sessions,
108
+ :processed => processed,
109
+ :uptime => uptime
110
+ }
111
+ end
112
+ else
113
+ if /-+\s+(.+)\s+-+/ =~ line
114
+ section = $1
115
+ sections[section] = []
116
+ end
117
+ end
118
+ end
119
+ sections
120
+ end
121
+
122
+ def vlabel
123
+ case @type
124
+ when "sessions"
125
+ "number of processing sessions"
126
+ when "processed"
127
+ "number of processed sessions"
128
+ when "uptime"
129
+ "uptime by minutes"
130
+ else
131
+ "unknown"
132
+ end
133
+ end
134
+
135
+ def config
136
+ success, result = passenger_status
137
+ unless success
138
+ puts result
139
+ exit(false)
140
+ end
141
+
142
+ if @label
143
+ title = "Passenger: #{@label}: #{@type}: #{@domain}"
144
+ else
145
+ title = "Passenger: #{@type}: #{@domain}"
146
+ end
147
+ sections = parse_result(result)
148
+ puts(<<-EOC)
149
+ graph_title #{title}
150
+ graph_category passenger
151
+ graph_info Passenger #{@domain} #{@type}
152
+ graph_vlabel #{vlabel}
153
+
154
+ EOC
155
+ sections["Domains"].each do |domain, processes|
156
+ next if domain != @domain
157
+ processes.sort_by do |attributes|
158
+ attributes[:pid]
159
+ end.each_with_index do |attributes, i|
160
+ puts("#{@type}#{i}.label #{i} (PID #{attributes[:pid]})")
161
+ end
162
+ end
163
+ end
164
+
165
+ def report
166
+ success, result = passenger_status
167
+ unless success
168
+ puts result
169
+ exit(false)
170
+ end
171
+
172
+ sections = parse_result(result)
173
+ sections["Domains"].each do |domain, processes|
174
+ next if domain != @domain
175
+ processes.sort_by do |attributes|
176
+ attributes[:pid]
177
+ end.each_with_index do |attributes, i|
178
+ puts("#{@type}#{i}.value #{attributes[@type.to_sym]}")
179
+ end
180
+ end
181
+ end
182
+
183
+ case mode
184
+ when "auto", "autoconf", "detect"
185
+ success, result = passenger_status
186
+ if success
187
+ puts "yes"
188
+ exit(true)
189
+ else
190
+ puts "no (#{result})"
191
+ exit(false)
192
+ end
193
+ when "suggest"
194
+ success, result = passenger_status
195
+ if success
196
+ sections = parse_result(result)
197
+ domains = sections["Domains"]
198
+ if domains
199
+ domains.each do |domain, processes|
200
+ puts "#{domain}_sessions"
201
+ puts "#{domain}_processed"
202
+ puts "#{domain}_uptime"
203
+ end
204
+ exit(true)
205
+ else
206
+ puts "no domain: #{result.inspect}"
207
+ exit(false)
208
+ end
209
+ else
210
+ puts result
211
+ exit(false)
212
+ end
213
+ else
214
+ if @domain.nil?
215
+ puts "no domain"
216
+ exit(false)
217
+ end
218
+ if @type.nil?
219
+ puts "no type: #{@domain}"
220
+ exit(false)
221
+ end
222
+ case mode
223
+ when "config"
224
+ config
225
+ else
226
+ report
227
+ end
228
+ end