racknga 0.9.1 → 0.9.2
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.
- data/Gemfile +30 -0
- data/README.textile +52 -0
- data/Rakefile +237 -117
- data/doc/text/news.textile +32 -0
- data/lib/racknga/cache_database.rb +18 -3
- data/lib/racknga/exception_mail_notifier.rb +76 -15
- data/lib/racknga/log_database.rb +21 -4
- data/lib/racknga/middleware/cache.rb +75 -14
- data/lib/racknga/middleware/deflater.rb +37 -0
- data/lib/racknga/middleware/exception_notifier.rb +17 -0
- data/lib/racknga/middleware/instance_name.rb +134 -0
- data/lib/racknga/middleware/jsonp.rb +82 -10
- data/lib/racknga/middleware/log.rb +18 -3
- data/lib/racknga/middleware/range.rb +10 -0
- data/lib/racknga/nginx_access_log_parser.rb +139 -0
- data/lib/racknga/reverse_line_reader.rb +73 -0
- data/lib/racknga/version.rb +2 -2
- data/lib/racknga/will_paginate.rb +1 -0
- data/lib/racknga.rb +3 -1
- data/license/lgpl-2.1.txt +502 -0
- data/munin/plugins/{passenger_domain_ → passenger_application_} +63 -47
- data/test/racknga-test-utils.rb +9 -12
- data/test/run-test.rb +5 -10
- data/test/test-middleware-cache.rb +77 -0
- data/test/test-middleware-instance-name.rb +98 -0
- data/test/test-middleware-jsonp.rb +32 -9
- data/test/test-middleware-range.rb +5 -4
- data/test/test-nginx-log-parser.rb +214 -0
- metadata +106 -56
- data/NEWS.ja.rdoc +0 -16
- data/NEWS.rdoc +0 -9
- data/README.ja.rdoc +0 -80
- data/README.rdoc +0 -51
- data/html/footer.html.erb +0 -33
- data/html/head.html.erb +0 -4
- data/html/header.html.erb +0 -17
- data/test/fixtures/rabbit-theme-change.ogv +0 -0
@@ -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
|
@@ -29,21 +29,50 @@ module Racknga
|
|
29
29
|
class ExceptionMailNotifier
|
30
30
|
def initialize(options)
|
31
31
|
@options = Utils.normalize_options(options || {})
|
32
|
+
reset_limitation
|
32
33
|
end
|
33
34
|
|
34
35
|
def notify(exception, environment)
|
35
|
-
host = @options[:host] || "localhost"
|
36
36
|
return if to.empty?
|
37
|
-
|
37
|
+
|
38
|
+
if limitation_expired?
|
39
|
+
send_summaries unless @summaries.empty?
|
40
|
+
reset_limitation
|
41
|
+
end
|
42
|
+
|
43
|
+
if @mail_count < max_mail_count_in_limit_duration
|
44
|
+
send_notification(exception, environment)
|
45
|
+
else
|
46
|
+
@summaries << summarize(exception, environment)
|
47
|
+
end
|
48
|
+
|
49
|
+
@mail_count += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def reset_limitation
|
54
|
+
@mail_count = 0
|
55
|
+
@count_start_time = Time.now
|
56
|
+
@summaries = []
|
57
|
+
end
|
58
|
+
|
59
|
+
def limitation_expired?
|
60
|
+
(Time.now - @count_start_time) > limit_duration
|
61
|
+
end
|
62
|
+
|
63
|
+
def send(mail)
|
64
|
+
host = @options[:host] || "localhost"
|
38
65
|
Net::SMTP.start(host, @options[:port]) do |smtp|
|
39
66
|
smtp.send_message(mail, from, *to)
|
40
67
|
end
|
41
68
|
end
|
42
69
|
|
43
|
-
|
44
|
-
|
45
|
-
header =
|
46
|
-
|
70
|
+
def create_mail(options)
|
71
|
+
subject = [@options[:subject_label], options[:subject]].compact.join(' ')
|
72
|
+
header = header(:subject => subject)
|
73
|
+
|
74
|
+
body = options[:body]
|
75
|
+
|
47
76
|
mail = "#{header}\r\n#{body}"
|
48
77
|
mail.force_encoding("utf-8")
|
49
78
|
begin
|
@@ -53,24 +82,28 @@ module Racknga
|
|
53
82
|
mail.force_encoding("ASCII-8BIT")
|
54
83
|
end
|
55
84
|
|
56
|
-
def
|
85
|
+
def header(options)
|
57
86
|
<<-EOH
|
58
87
|
MIME-Version: 1.0
|
59
88
|
Content-Type: Text/Plain; charset=#{charset}
|
60
89
|
Content-Transfer-Encoding: #{transfer_encoding}
|
61
90
|
From: #{from}
|
62
91
|
To: #{to.join(', ')}
|
63
|
-
Subject: #{encode_subject(subject
|
92
|
+
Subject: #{encode_subject(options[:subject])}
|
64
93
|
Date: #{Time.now.rfc2822}
|
65
94
|
EOH
|
66
95
|
end
|
67
96
|
|
68
|
-
def
|
97
|
+
def send_notification(exception, environment)
|
98
|
+
mail = create_mail(:subject => exception.to_s,
|
99
|
+
:body => notification_body(exception, environment))
|
100
|
+
send(mail)
|
101
|
+
end
|
102
|
+
|
103
|
+
def notification_body(exception, environment)
|
69
104
|
request = Rack::Request.new(environment)
|
70
105
|
body = <<-EOB
|
71
|
-
|
72
|
-
--
|
73
|
-
#{exception.class}: #{exception}
|
106
|
+
#{summarize(exception, environment)}
|
74
107
|
--
|
75
108
|
#{exception.backtrace.join("\n")}
|
76
109
|
EOB
|
@@ -98,8 +131,26 @@ EOE
|
|
98
131
|
body
|
99
132
|
end
|
100
133
|
|
101
|
-
def
|
102
|
-
|
134
|
+
def send_summaries
|
135
|
+
subject = "summaries of #{@summaries.size} notifications"
|
136
|
+
mail = create_mail(:subject => subject,
|
137
|
+
:body => report_body)
|
138
|
+
send(mail)
|
139
|
+
end
|
140
|
+
|
141
|
+
def report_body
|
142
|
+
@summaries[0..10].join("\n\n")
|
143
|
+
end
|
144
|
+
|
145
|
+
def summarize(exception, environment)
|
146
|
+
request = Rack::Request.new(environment)
|
147
|
+
<<-EOB
|
148
|
+
Timestamp: #{Time.now.rfc2822}
|
149
|
+
--
|
150
|
+
URL: #{request.url}
|
151
|
+
--
|
152
|
+
#{exception.class}: #{exception}
|
153
|
+
EOB
|
103
154
|
end
|
104
155
|
|
105
156
|
def to
|
@@ -125,6 +176,16 @@ EOE
|
|
125
176
|
@options[:charset] || 'utf-8'
|
126
177
|
end
|
127
178
|
|
179
|
+
DEFAULT_MAX_MAIL_COUNT_IN_LIMIT_DURATION = 2
|
180
|
+
def max_mail_count_in_limit_duration
|
181
|
+
@options[:max_mail_count_in_limit_duration] || DEFAULT_MAX_MAIL_COUNT_IN_LIMIT_DURATION
|
182
|
+
end
|
183
|
+
|
184
|
+
DEFAULT_LIMIT_DURATION = 60 # one minute
|
185
|
+
def limit_duration
|
186
|
+
@options[:limit_duration] || DEFAULT_LIMIT_DURATION
|
187
|
+
end
|
188
|
+
|
128
189
|
def transfer_encoding
|
129
190
|
case charset
|
130
191
|
when /\Autf-8\z/i
|
data/lib/racknga/log_database.rb
CHANGED
@@ -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
|
@@ -21,7 +21,13 @@ require 'fileutils'
|
|
21
21
|
require 'groonga'
|
22
22
|
|
23
23
|
module Racknga
|
24
|
+
# This is a log database based on groonga. It is used by
|
25
|
+
# Racknga::Middleware::Log.
|
26
|
+
#
|
27
|
+
# Normally, #purge_old_responses is only used for log
|
28
|
+
# maintenance.
|
24
29
|
class LogDatabase
|
30
|
+
# @param [String] database_path the path for log database.
|
25
31
|
def initialize(database_path)
|
26
32
|
@database_path = database_path
|
27
33
|
@context = Groonga::Context.new(:encoding => :none)
|
@@ -45,12 +51,23 @@ module Racknga
|
|
45
51
|
@database.close
|
46
52
|
end
|
47
53
|
|
54
|
+
# Purges old responses. To clear old logs, you should
|
55
|
+
# call this method. All records created before
|
56
|
+
# +base_time+ are removed.
|
57
|
+
#
|
58
|
+
# You can call this method by the different
|
59
|
+
# process from your Rack application
|
60
|
+
# process. (e.g. cron.) It's multi process safe.
|
61
|
+
#
|
62
|
+
# @param [Time] base_time the oldest record time to be
|
63
|
+
# removed. The default value is 1 day ago.
|
48
64
|
def purge_old_entries(base_time=nil)
|
49
65
|
base_time ||= Time.now - 60 * 60 * 24
|
50
|
-
entries.select do |record|
|
66
|
+
target_entries = entries.select do |record|
|
51
67
|
record.time_stamp < base_time
|
52
|
-
end
|
53
|
-
|
68
|
+
end
|
69
|
+
target_entries.each do |entry|
|
70
|
+
entry.key.delete
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|
@@ -16,17 +16,36 @@
|
|
16
16
|
# License along with this library; if not, write to the Free Software
|
17
17
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
18
18
|
|
19
|
-
require 'digest
|
19
|
+
require 'digest'
|
20
20
|
require 'yaml'
|
21
21
|
require 'racknga/cache_database'
|
22
22
|
|
23
23
|
module Racknga
|
24
24
|
module Middleware
|
25
|
+
# This is a helper middleware for
|
26
|
+
# Racknga::Middleware::Cache.
|
27
|
+
#
|
28
|
+
# If your Rack application provides different views to
|
29
|
+
# mobile user agent and PC user agent in the same URL,
|
30
|
+
# this middleware is useful. Your Rack application can
|
31
|
+
# has different caches for mobile user agent and PC user
|
32
|
+
# agent.
|
33
|
+
#
|
34
|
+
# This middleware requires jpmobile.
|
35
|
+
#
|
36
|
+
# Usage:
|
37
|
+
# use Racnkga::Middleware::PerUserAgentCache
|
38
|
+
# use Racnkga::Middleware::Cache, :database_path => "var/cache/db"
|
39
|
+
# run YourApplication
|
40
|
+
#
|
41
|
+
# @see http://jpmobile-rails.org/ jpmobile
|
42
|
+
# @see Racknga::Middleware::Cache
|
25
43
|
class PerUserAgentCache
|
26
44
|
def initialize(application)
|
27
45
|
@application = application
|
28
46
|
end
|
29
47
|
|
48
|
+
# For Rack.
|
30
49
|
def call(environment)
|
31
50
|
mobile = environment["rack.jpmobile"]
|
32
51
|
if mobile
|
@@ -35,16 +54,47 @@ module Racknga
|
|
35
54
|
else
|
36
55
|
user_agent_key = "pc"
|
37
56
|
end
|
38
|
-
key = environment[Cache::
|
39
|
-
environment[Cache::
|
57
|
+
key = environment[Cache::KEY]
|
58
|
+
environment[Cache::KEY] = [key, user_agent_key].join(":")
|
40
59
|
@application.call(environment)
|
41
60
|
end
|
42
61
|
end
|
43
62
|
|
63
|
+
# This is a middleware that provides page cache.
|
64
|
+
#
|
65
|
+
# This stores page contents into a groonga
|
66
|
+
# database. A groonga database can access by multi
|
67
|
+
# process. It means that your Rack application processes
|
68
|
+
# can share the same cache. For example, Passenger runs
|
69
|
+
# your Rack application with multi processes.
|
70
|
+
#
|
71
|
+
# Cache key is the request URL by default. It can be
|
72
|
+
# customized by env[Racknga::Cache::KEY]. For example,
|
73
|
+
# Racknga::Middleware::PerUserAgentCache and
|
74
|
+
# Racknga::Middleware::JSONP use it.
|
75
|
+
#
|
76
|
+
# This only caches the following responses:
|
77
|
+
# * 200 status response.
|
78
|
+
# * text/*, */json, */xml or */*+xml content type response.
|
79
|
+
#
|
80
|
+
# Usage:
|
81
|
+
# use Racnkga::Middleware::Cache, :database_path => "var/cache/db"
|
82
|
+
# run YourApplication
|
83
|
+
#
|
84
|
+
# @see Racknga::Middleware::PerUserAgentCache
|
85
|
+
# @see Racknga::Middleware::JSONP
|
86
|
+
# @see Racknga::Middleware::Deflater
|
87
|
+
# @see Racknga::CacheDatabase
|
44
88
|
class Cache
|
45
|
-
|
46
|
-
|
89
|
+
KEY = "racknga.cache.key"
|
90
|
+
START_TIME = "racknga.cache.start_time"
|
47
91
|
|
92
|
+
# @return [Racknga::CacheDatabase] the database used
|
93
|
+
# by this middleware.
|
94
|
+
attr_reader :database
|
95
|
+
|
96
|
+
# @option options [String] :database_path the database
|
97
|
+
# path to be stored caches.
|
48
98
|
def initialize(application, options={})
|
49
99
|
@application = application
|
50
100
|
@options = Utils.normalize_options(options || {})
|
@@ -53,12 +103,13 @@ module Racknga
|
|
53
103
|
@database = CacheDatabase.new(database_path)
|
54
104
|
end
|
55
105
|
|
106
|
+
# For Rack.
|
56
107
|
def call(environment)
|
57
108
|
request = Rack::Request.new(environment)
|
58
109
|
return @application.call(environment) unless use_cache?(request)
|
59
110
|
age = @database.configuration.age
|
60
|
-
key = environment[
|
61
|
-
environment[
|
111
|
+
key = normalize_key(environment[KEY] || request.fullpath)
|
112
|
+
environment[START_TIME] = Time.now
|
62
113
|
cache = @database.responses
|
63
114
|
record = cache[key]
|
64
115
|
if record and record.age == age
|
@@ -68,10 +119,12 @@ module Racknga
|
|
68
119
|
end
|
69
120
|
end
|
70
121
|
|
122
|
+
# ensures creating cache database.
|
71
123
|
def ensure_database
|
72
124
|
@database.ensure_database
|
73
125
|
end
|
74
126
|
|
127
|
+
# close the cache database.
|
75
128
|
def close_database
|
76
129
|
@database.close_database
|
77
130
|
end
|
@@ -97,6 +150,14 @@ module Racknga
|
|
97
150
|
true
|
98
151
|
end
|
99
152
|
|
153
|
+
def normalize_key(key)
|
154
|
+
if key.size > 4096
|
155
|
+
Digest::SHA1.hexdigest(key).force_encoding("ASCII-8BIT")
|
156
|
+
else
|
157
|
+
key
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
100
161
|
def handle_request(cache, key, age, request)
|
101
162
|
status, headers, body = @application.call(request.env)
|
102
163
|
if skip_caching_response?(status, headers, body)
|
@@ -142,13 +203,13 @@ module Racknga
|
|
142
203
|
end
|
143
204
|
|
144
205
|
def compute_checksum(status, encoded_headers, encoded_body)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
206
|
+
checksum = Digest::SHA1.new
|
207
|
+
checksum << status.to_s
|
208
|
+
checksum << ":"
|
209
|
+
checksum << encoded_headers
|
210
|
+
checksum << ":"
|
211
|
+
checksum << encoded_body
|
212
|
+
checksum.hexdigest.force_encoding("ASCII-8BIT")
|
152
213
|
end
|
153
214
|
|
154
215
|
def valid_cache?(status, encoded_headers, encoded_body, checksum)
|
@@ -18,6 +18,42 @@
|
|
18
18
|
|
19
19
|
module Racknga
|
20
20
|
module Middleware
|
21
|
+
# This is a middleware that deflates response except for
|
22
|
+
# IE6. If your Rack application need support IE6, use
|
23
|
+
# this middleware instead of Rack::Deflater.
|
24
|
+
#
|
25
|
+
# Usage:
|
26
|
+
# require "racknga"
|
27
|
+
#
|
28
|
+
# use Racknga::Middleware::Deflater
|
29
|
+
# run YourApplication
|
30
|
+
#
|
31
|
+
# You can use this middleware with
|
32
|
+
# Racknga::Middleware::Cache. You *should* use this
|
33
|
+
# middleware before the cache middleware:
|
34
|
+
# use Racknga::Middleawre::Deflater
|
35
|
+
# use Racknga::Middleawre::Cache, :database_path => "var/cache/db"
|
36
|
+
# run YourApplication
|
37
|
+
#
|
38
|
+
# If you use this middleware after the cache middleware,
|
39
|
+
# you get two problems. It's the first problem pattern
|
40
|
+
# that the cache middleware may return deflated response
|
41
|
+
# to IE6. It's the second problem pattern that the cache
|
42
|
+
# middleware may return not deflated response to no IE6
|
43
|
+
# user agent. Here are examples:
|
44
|
+
#
|
45
|
+
# Problem case:
|
46
|
+
# use Racknga::Middleawre::Cache, :database_path => "var/cache/db"
|
47
|
+
# use Racknga::Middleawre::Deflater
|
48
|
+
# run YourApplication
|
49
|
+
#
|
50
|
+
# Problem pattern1:
|
51
|
+
# http://localhost:9292/ by Firefox -> no cache. cache deflated response.
|
52
|
+
# http://localhost:9292/ by IE6 -> use deflated response cache.
|
53
|
+
#
|
54
|
+
# Problem pattern2:
|
55
|
+
# http://localhost:9292/ by IE6 -> no cache. cache not deflated response.
|
56
|
+
# http://localhost:9292/ by Firefox -> use not deflated response cache.
|
21
57
|
class Deflater
|
22
58
|
def initialize(application, options={})
|
23
59
|
@application = application
|
@@ -25,6 +61,7 @@ module Racknga
|
|
25
61
|
@options = Utils.normalize_options(options || {})
|
26
62
|
end
|
27
63
|
|
64
|
+
# For Rack.
|
28
65
|
def call(environment)
|
29
66
|
if ie6?(environment)
|
30
67
|
@application.call(environment)
|
@@ -20,6 +20,22 @@ require 'racknga/exception_mail_notifier'
|
|
20
20
|
|
21
21
|
module Racknga
|
22
22
|
module Middleware
|
23
|
+
# This is a middleware that mails exception details on
|
24
|
+
# error. It's useful for finding your Rack application
|
25
|
+
# troubles.
|
26
|
+
#
|
27
|
+
# Usage:
|
28
|
+
# require "racknga"
|
29
|
+
# require "racknga/middleware/exception_notifier"
|
30
|
+
#
|
31
|
+
# notifier_options = {
|
32
|
+
# :subject_label => "[YourApplication]",
|
33
|
+
# :from => "reporter@example.com",
|
34
|
+
# :to => "maintainers@example.com",
|
35
|
+
# }
|
36
|
+
# notifiers = [Racknga::ExceptionMailNotifier.new(notifier_options)]
|
37
|
+
# use Racknga::Middleware::ExceptionNotifier, :notifiers => notifiers
|
38
|
+
# run YourApplication
|
23
39
|
class ExceptionNotifier
|
24
40
|
def initialize(application, options={})
|
25
41
|
@application = application
|
@@ -27,6 +43,7 @@ module Racknga
|
|
27
43
|
@notifiers = @options[:notifiers] || []
|
28
44
|
end
|
29
45
|
|
46
|
+
# For Rack.
|
30
47
|
def call(environment)
|
31
48
|
@application.call(environment)
|
32
49
|
rescue Exception => exception
|
@@ -0,0 +1,134 @@
|
|
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
|
+
# This is a middleware that adds "X-Responsed-By" header
|
22
|
+
# to responses. It's useful to determine responded
|
23
|
+
# server when your Rack applications are deployed behind
|
24
|
+
# load balancers.
|
25
|
+
#
|
26
|
+
# Usage:
|
27
|
+
# require "racknga"
|
28
|
+
# use Racknga::Middleware::InstanceName
|
29
|
+
# run YourApplication
|
30
|
+
class InstanceName
|
31
|
+
attr_reader :header
|
32
|
+
def initialize(application, options={})
|
33
|
+
@application = application
|
34
|
+
@options = options
|
35
|
+
|
36
|
+
@header = construct_header.freeze
|
37
|
+
@headers = construct_headers.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
# For Rack.
|
41
|
+
def call(environment)
|
42
|
+
response = @application.call(environment).to_a
|
43
|
+
|
44
|
+
[
|
45
|
+
response[0],
|
46
|
+
response[1].merge(@headers),
|
47
|
+
response[2],
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def application_name
|
52
|
+
@options[:application_name] || @application.class.name
|
53
|
+
end
|
54
|
+
|
55
|
+
def version
|
56
|
+
@options[:version]
|
57
|
+
end
|
58
|
+
|
59
|
+
def revision
|
60
|
+
`git describe --abbrev=7 HEAD`.strip # XXX be SCM-agonostic
|
61
|
+
end
|
62
|
+
|
63
|
+
def server
|
64
|
+
`hostname`.strip
|
65
|
+
end
|
66
|
+
|
67
|
+
def user
|
68
|
+
`id --user --name`.strip
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
DEFAULT_HEADER_NAME = "X-Responsed-By"
|
73
|
+
def header_name
|
74
|
+
@options[:header_name] || DEFAULT_HEADER_NAME
|
75
|
+
end
|
76
|
+
|
77
|
+
def construct_headers
|
78
|
+
{
|
79
|
+
header_name => header,
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def construct_header
|
84
|
+
format_header(format_application_name(application_name),
|
85
|
+
format_version(version),
|
86
|
+
format_revision(revision),
|
87
|
+
format_server(server),
|
88
|
+
format_user(user))
|
89
|
+
end
|
90
|
+
|
91
|
+
def format_header(*arguments)
|
92
|
+
arguments.compact.join(" ")
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_application_name(name)
|
96
|
+
format_if_possible(name) do
|
97
|
+
"#{name}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def format_version(version)
|
102
|
+
format_if_possible(version) do
|
103
|
+
"v#{version}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def format_revision(revision)
|
108
|
+
format_if_possible(revision) do
|
109
|
+
"(at #{revision})"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def format_server(server)
|
114
|
+
format_if_possible(server) do
|
115
|
+
"on #{server}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def format_user(user)
|
120
|
+
format_if_possible(user) do
|
121
|
+
"by #{user}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def format_if_possible(data)
|
126
|
+
if data and (data.respond_to?(:to_s) and not data.to_s.empty?)
|
127
|
+
result = yield
|
128
|
+
end
|
129
|
+
|
130
|
+
result
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -18,26 +18,87 @@
|
|
18
18
|
|
19
19
|
module Racknga
|
20
20
|
module Middleware
|
21
|
+
# This is a middleware that provides JSONP support.
|
22
|
+
#
|
23
|
+
# If you use this middleware, your Rack application just
|
24
|
+
# returns JSON response.
|
25
|
+
#
|
26
|
+
# Usage:
|
27
|
+
# require "racknga"
|
28
|
+
#
|
29
|
+
# use Rack::ContentLength
|
30
|
+
# use Racknga::Middleware::JSONP
|
31
|
+
# json_application = Proc.new do |env|
|
32
|
+
# [200,
|
33
|
+
# {"Content-Type" => "application/json"},
|
34
|
+
# ['{"Hello": "World"}']]
|
35
|
+
# end
|
36
|
+
# run json_application
|
37
|
+
#
|
38
|
+
# Results:
|
39
|
+
# % curl 'http://localhost:9292/'
|
40
|
+
# {"Hello": "World"}
|
41
|
+
# % curl 'http://localhost:9292/?callback=function'
|
42
|
+
# function({"Hello": "World"})
|
43
|
+
#
|
44
|
+
# You can use this middleware with
|
45
|
+
# Racknga::Middleware::Cache. You *should* use this
|
46
|
+
# middleware before the cache middleware:
|
47
|
+
# use Racknga::Middleawre::JSONP
|
48
|
+
# use Racknga::Middleawre::Cache, :database_path => "var/cache/db"
|
49
|
+
# run YourApplication
|
50
|
+
#
|
51
|
+
# If you use this middleware after the cache middleware,
|
52
|
+
# the cache middleware will cache many responses that
|
53
|
+
# just only differ callback parameter value. Here are
|
54
|
+
# examples:
|
55
|
+
#
|
56
|
+
# Recommended case:
|
57
|
+
# use Racknga::Middleawre::JSONP
|
58
|
+
# use Racknga::Middleawre::Cache, :database_path => "var/cache/db"
|
59
|
+
# run YourApplication
|
60
|
+
#
|
61
|
+
# Requests:
|
62
|
+
# http://localhost:9292/ -> no cache. cached.
|
63
|
+
# http://localhost:9292/?callback=function1 -> use cache.
|
64
|
+
# http://localhost:9292/?callback=function2 -> use cache.
|
65
|
+
# http://localhost:9292/?callback=function3 -> use cache.
|
66
|
+
# http://localhost:9292/?callback=function1 -> use cache.
|
67
|
+
#
|
68
|
+
# Not recommended case:
|
69
|
+
# use Racknga::Middleawre::Cache, :database_path => "var/cache/db"
|
70
|
+
# use Racknga::Middleawre::JSONP
|
71
|
+
# run YourApplication
|
72
|
+
#
|
73
|
+
# Requests:
|
74
|
+
# http://localhost:9292/ -> no cache. cached.
|
75
|
+
# http://localhost:9292/?callback=function1 -> no cache. cached.
|
76
|
+
# http://localhost:9292/?callback=function2 -> no cache. cached.
|
77
|
+
# http://localhost:9292/?callback=function3 -> no cache. cached.
|
78
|
+
# http://localhost:9292/?callback=function1 -> use cache.
|
21
79
|
class JSONP
|
22
80
|
def initialize(application)
|
23
81
|
@application = application
|
24
82
|
end
|
25
83
|
|
84
|
+
# For Rack.
|
26
85
|
def call(environment)
|
27
86
|
request = Rack::Request.new(environment)
|
28
87
|
callback = request["callback"]
|
29
88
|
update_cache_key(request) if callback
|
30
89
|
status, headers, body = @application.call(environment)
|
31
90
|
return [status, headers, body] unless callback
|
32
|
-
|
91
|
+
header_hash = Rack::Utils::HeaderHash.new(headers)
|
92
|
+
return [status, headers, body] unless json_response?(header_hash)
|
33
93
|
body = Writer.new(callback, body)
|
34
|
-
|
94
|
+
update_content_type(header_hash)
|
95
|
+
[status, header_hash, body]
|
35
96
|
end
|
36
97
|
|
37
98
|
private
|
38
99
|
def update_cache_key(request)
|
39
100
|
return unless Middleware.const_defined?(:Cache)
|
40
|
-
|
101
|
+
cache_key = Cache::KEY
|
41
102
|
|
42
103
|
path = request.fullpath
|
43
104
|
path, parameters = path.split(/\?/, 2)
|
@@ -49,17 +110,28 @@ module Racknga
|
|
49
110
|
path << "?" << parameters unless parameters.empty?
|
50
111
|
end
|
51
112
|
|
52
|
-
key = request.env[
|
53
|
-
request.env[
|
113
|
+
key = request.env[cache_key]
|
114
|
+
request.env[cache_key] = [key, path].compact.join(":")
|
54
115
|
end
|
55
116
|
|
56
|
-
def json_response?(
|
57
|
-
content_type =
|
58
|
-
|
59
|
-
|
60
|
-
|
117
|
+
def json_response?(header_hash)
|
118
|
+
content_type = header_hash["Content-Type"]
|
119
|
+
media_type = content_type.split(/\s*;\s*/, 2).first.downcase
|
120
|
+
media_type == "application/json" or
|
121
|
+
media_type == "application/javascript" or
|
122
|
+
media_type == "text/javascript"
|
61
123
|
end
|
62
124
|
|
125
|
+
def update_content_type(header_hash)
|
126
|
+
content_type = header_hash["Content-Type"]
|
127
|
+
media_type, parameters = content_type.split(/\s*;\s*/, 2)
|
128
|
+
# We should use application/javascript not
|
129
|
+
# text/javascript when all IE <= 8 are deprecated. :<
|
130
|
+
updated_content_type = ["text/javascript", parameters].compact.join("; ")
|
131
|
+
header_hash["Content-Type"] = updated_content_type
|
132
|
+
end
|
133
|
+
|
134
|
+
# @private
|
63
135
|
class Writer
|
64
136
|
def initialize(callback, body)
|
65
137
|
@callback = callback
|