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.
- data/NEWS.ja.rdoc +11 -0
- data/NEWS.rdoc +4 -0
- data/README.ja.rdoc +42 -6
- data/README.rdoc +8 -1
- data/Rakefile +1 -1
- data/html/footer.html.erb +5 -0
- data/lib/racknga/cache_database.rb +40 -6
- data/lib/racknga/exception_mail_notifier.rb +3 -1
- data/lib/racknga/log_database.rb +108 -0
- data/lib/racknga/middleware/cache.rb +77 -28
- data/lib/racknga/middleware/deflater.rb +2 -1
- data/lib/racknga/middleware/exception_notifier.rb +2 -1
- data/lib/racknga/middleware/jsonp.rb +77 -0
- data/lib/racknga/middleware/log.rb +94 -0
- data/lib/racknga/middleware/range.rb +159 -0
- data/lib/racknga/utils.rb +2 -1
- data/lib/racknga/version.rb +3 -2
- data/lib/racknga/will_paginate.rb +47 -0
- data/lib/racknga.rb +4 -3
- data/munin/plugins/passenger_domain_ +228 -0
- data/munin/plugins/passenger_status +141 -0
- data/test/fixtures/rabbit-theme-change.ogv +0 -0
- data/test/racknga-test-utils.rb +42 -0
- data/test/run-test.rb +56 -0
- data/test/test-middleware-jsonp.rb +78 -0
- data/test/test-middleware-range.rb +160 -0
- metadata +36 -12
- data/license/LGPL +0 -504
data/NEWS.ja.rdoc
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
= お知らせ
|
2
2
|
|
3
|
+
== 0.9.1: 2010-11-11
|
4
|
+
|
5
|
+
* キャッシュの正当性チェックを強化。
|
6
|
+
* User-Agent毎のキャッシュをサポート。
|
7
|
+
* JSONPをサポートするミドルウェアを追加。
|
8
|
+
* HTTPのRangeをサポートするミドルウェアを追加。
|
9
|
+
* groongaデータベースにアクセスログを記録するミドルウェアを追加。
|
10
|
+
* Passengerの状態を視覚化するMuninプラグインを追加。
|
11
|
+
* ライセンスをLGPLv2.1からLGPLv2.1 or laterに変更。
|
12
|
+
* will_paginate対応。
|
13
|
+
|
3
14
|
== 0.9.0: 2010-07-04
|
4
15
|
|
5
16
|
* 最初のリリース!
|
data/NEWS.rdoc
CHANGED
data/README.ja.rdoc
CHANGED
@@ -6,7 +6,8 @@ racknga(らくんが)
|
|
6
6
|
|
7
7
|
== 説明
|
8
8
|
|
9
|
-
rroongaの機能を利用したRack
|
9
|
+
rroongaの機能を利用したRackのミドルウェアとrroongaの機能は利
|
10
|
+
用してませんが役に立つミドルウェアを提供します。
|
10
11
|
|
11
12
|
* rroonga: http://groonga.rubyforge.org/
|
12
13
|
* Rack: http://rack.rubyforge.org/
|
@@ -17,7 +18,11 @@ Kouhei Sutou:: <tt><kou@clear-code.com></tt>
|
|
17
18
|
|
18
19
|
== ライセンス
|
19
20
|
|
20
|
-
LGPL 2.1
|
21
|
+
LGPL 2.1またはそれ以降のバージョンです。詳しくは
|
22
|
+
license/lgpl-2.1.txtを見てください。
|
23
|
+
|
24
|
+
(コントリビュートされたパッチなども含み、Kouhei Sutouが
|
25
|
+
ライセンスを変更する権利を持ちます。)
|
21
26
|
|
22
27
|
== 依存ソフトウェア
|
23
28
|
|
@@ -25,13 +30,14 @@ LGPL 2.1です。詳しくはlicense/LGPLを見てください。
|
|
25
30
|
* rroonga
|
26
31
|
* Rack
|
27
32
|
|
28
|
-
|
33
|
+
=== あるとよいソフトウェア
|
29
34
|
|
30
|
-
|
35
|
+
* jpmobile
|
36
|
+
* will_paginate
|
31
37
|
|
32
|
-
==
|
38
|
+
== インストール
|
33
39
|
|
34
|
-
|
40
|
+
% sudo gem install racknga
|
35
41
|
|
36
42
|
== メーリングリスト
|
37
43
|
|
@@ -39,6 +45,36 @@ http://groonga.rubyforge.org/racknga/
|
|
39
45
|
|
40
46
|
http://lists.sourceforge.jp/mailman/listinfo/groonga-dev
|
41
47
|
|
48
|
+
== ドキュメント
|
49
|
+
|
50
|
+
RackngaにはRackアプリケーションに有用なミドルウェアをたくさん
|
51
|
+
提供しています。ここでは、オプションなど詳細な情報は
|
52
|
+
http://groonga.rubyforge.org/racknga/ を参照してください。
|
53
|
+
|
54
|
+
=== Racknga::Middleware::Cache
|
55
|
+
|
56
|
+
...
|
57
|
+
|
58
|
+
=== Racknga::Middleware::Deflater
|
59
|
+
|
60
|
+
...
|
61
|
+
|
62
|
+
=== Racknga::Middleware::ExceptionNotifier
|
63
|
+
|
64
|
+
...
|
65
|
+
|
66
|
+
=== Racknga::Middleware::JSONP
|
67
|
+
|
68
|
+
...
|
69
|
+
|
70
|
+
=== Racknga::Middleware::Range
|
71
|
+
|
72
|
+
...
|
73
|
+
|
74
|
+
=== Racknga::Middleware::Log
|
75
|
+
|
76
|
+
...
|
77
|
+
|
42
78
|
== 感謝
|
43
79
|
|
44
80
|
* ...
|
data/README.rdoc
CHANGED
@@ -17,7 +17,10 @@ Kouhei Sutou:: <tt><kou@clear-code.com></tt>
|
|
17
17
|
|
18
18
|
== License
|
19
19
|
|
20
|
-
LGPL 2.1. See license/
|
20
|
+
LGPL 2.1 or later. See license/lgpl-2.1.txt for details.
|
21
|
+
|
22
|
+
(Kouhei Sutou has a right to change the license
|
23
|
+
inclidng contributed patches.)
|
21
24
|
|
22
25
|
== Dependencies
|
23
26
|
|
@@ -25,6 +28,10 @@ LGPL 2.1. See license/LGPL for details.
|
|
25
28
|
* rroonga
|
26
29
|
* Rack
|
27
30
|
|
31
|
+
== Suggested libraries
|
32
|
+
|
33
|
+
* will_paginate
|
34
|
+
|
28
35
|
== Install
|
29
36
|
|
30
37
|
% sudo gem install racknga
|
data/Rakefile
CHANGED
data/html/footer.html.erb
CHANGED
@@ -6,6 +6,11 @@
|
|
6
6
|
<img src="/rubyforge.png" width="120" height="24" border="0" alt="Ruby/groongaプロジェクトはRubyForge.orgにホスティングしてもらっています。" />
|
7
7
|
</a>
|
8
8
|
</p>
|
9
|
+
<p id="sponsor-github">
|
10
|
+
<a href="http://github.com/ranguba/">
|
11
|
+
ラングバプロジェクトはGitHubにホスティングしてもらっています。
|
12
|
+
</a>
|
13
|
+
</p>
|
9
14
|
<p id="sponsor-tango">
|
10
15
|
<a href="http://tango.freedesktop.org/">
|
11
16
|
<img width="120" height="53" border="0" alt="Tango Desktop Projectのアイコンを利用しています。" src="/tango-logo.png" />
|
@@ -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
|
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,6 +16,10 @@
|
|
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
|
|
19
|
+
require 'fileutils'
|
20
|
+
|
21
|
+
require 'groonga'
|
22
|
+
|
18
23
|
module Racknga
|
19
24
|
class CacheDatabase
|
20
25
|
def initialize(database_path)
|
@@ -27,10 +32,22 @@ module Racknga
|
|
27
32
|
@context["Responses"]
|
28
33
|
end
|
29
34
|
|
30
|
-
def
|
31
|
-
|
35
|
+
def configurations
|
36
|
+
@context["Configurations"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def configuration
|
40
|
+
configurations["default"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def purge_old_responses
|
44
|
+
age_modulo = 2 ** 32
|
45
|
+
age = configuration.age
|
46
|
+
previous_age = (age - 1).modulo(age_modulo)
|
47
|
+
configuration.age = (age + 1).modulo(age_modulo)
|
48
|
+
|
32
49
|
responses.each do |response|
|
33
|
-
response.delete if response.
|
50
|
+
response.delete if response.age == previous_age
|
34
51
|
end
|
35
52
|
end
|
36
53
|
|
@@ -41,6 +58,7 @@ module Racknga
|
|
41
58
|
create_database
|
42
59
|
end
|
43
60
|
ensure_tables
|
61
|
+
ensure_default_configuration
|
44
62
|
end
|
45
63
|
|
46
64
|
def close_database
|
@@ -55,12 +73,24 @@ module Racknga
|
|
55
73
|
:key_type => "ShortText") do |table|
|
56
74
|
table.uint32("status")
|
57
75
|
table.short_text("headers")
|
58
|
-
table.text("body")
|
76
|
+
table.text("body", :compress => :zlib)
|
77
|
+
table.short_text("checksum")
|
78
|
+
table.uint32("age")
|
59
79
|
table.time("created_at")
|
60
80
|
end
|
61
81
|
end
|
62
82
|
end
|
63
83
|
|
84
|
+
def create_configurations_table
|
85
|
+
Groonga::Schema.define(:context => @context) do |schema|
|
86
|
+
schema.create_table("Configurations",
|
87
|
+
:type => :hash,
|
88
|
+
:key_type => "ShortText") do |table|
|
89
|
+
table.uint32("age")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
64
94
|
def create_database
|
65
95
|
FileUtils.mkdir_p(File.dirname(@database_path))
|
66
96
|
@database = Groonga::Database.create(:path => @database_path,
|
@@ -68,8 +98,12 @@ module Racknga
|
|
68
98
|
end
|
69
99
|
|
70
100
|
def ensure_tables
|
71
|
-
|
101
|
+
create_configurations_table
|
72
102
|
create_responses_table
|
73
103
|
end
|
104
|
+
|
105
|
+
def ensure_default_configuration
|
106
|
+
configurations.add("default")
|
107
|
+
end
|
74
108
|
end
|
75
109
|
end
|
@@ -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
|
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
|
@@ -19,6 +20,7 @@ require 'time'
|
|
19
20
|
require 'net/smtp'
|
20
21
|
require 'etc'
|
21
22
|
require 'socket'
|
23
|
+
require 'nkf'
|
22
24
|
|
23
25
|
require 'racknga/utils'
|
24
26
|
|
@@ -0,0 +1,108 @@
|
|
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 'fileutils'
|
20
|
+
|
21
|
+
require 'groonga'
|
22
|
+
|
23
|
+
module Racknga
|
24
|
+
class LogDatabase
|
25
|
+
def initialize(database_path)
|
26
|
+
@database_path = database_path
|
27
|
+
@context = Groonga::Context.new(:encoding => :none)
|
28
|
+
ensure_database
|
29
|
+
end
|
30
|
+
|
31
|
+
def entries
|
32
|
+
@entries ||= @context["Entries"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def ensure_database
|
36
|
+
if File.exist?(@database_path)
|
37
|
+
@database = Groonga::Database.open(@database_path, :context => @context)
|
38
|
+
else
|
39
|
+
create_database
|
40
|
+
end
|
41
|
+
ensure_tables
|
42
|
+
end
|
43
|
+
|
44
|
+
def close_database
|
45
|
+
@database.close
|
46
|
+
end
|
47
|
+
|
48
|
+
def purge_old_entries(base_time=nil)
|
49
|
+
base_time ||= Time.now - 60 * 60 * 24
|
50
|
+
entries.select do |record|
|
51
|
+
record.time_stamp < base_time
|
52
|
+
end.each do |record|
|
53
|
+
record.key.delete
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def create_tables
|
59
|
+
Groonga::Schema.define(:context => @context) do |schema|
|
60
|
+
schema.create_table("Tags",
|
61
|
+
:type => :patricia_trie,
|
62
|
+
:key_type => "ShortText") do |table|
|
63
|
+
end
|
64
|
+
|
65
|
+
schema.create_table("Paths",
|
66
|
+
:type => :patricia_trie,
|
67
|
+
:key_type => "ShortText") do |table|
|
68
|
+
end
|
69
|
+
|
70
|
+
schema.create_table("UserAgents",
|
71
|
+
:type => :hash,
|
72
|
+
:key_type => "ShortText") do |table|
|
73
|
+
end
|
74
|
+
|
75
|
+
schema.create_table("Entries") do |table|
|
76
|
+
table.time("time_stamp")
|
77
|
+
table.reference("tag", "Tags")
|
78
|
+
table.reference("path", "Paths")
|
79
|
+
table.reference("user_agent", "UserAgents")
|
80
|
+
table.float("runtime")
|
81
|
+
table.short_text("message", :compress => :zlib)
|
82
|
+
end
|
83
|
+
|
84
|
+
schema.change_table("Tags") do |table|
|
85
|
+
table.index("Entries.tag")
|
86
|
+
end
|
87
|
+
|
88
|
+
schema.change_table("Paths") do |table|
|
89
|
+
table.index("Entries.path")
|
90
|
+
end
|
91
|
+
|
92
|
+
schema.change_table("UserAgents") do |table|
|
93
|
+
table.index("Entries.user_agent")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_database
|
99
|
+
FileUtils.mkdir_p(File.dirname(@database_path))
|
100
|
+
@database = Groonga::Database.create(:path => @database_path,
|
101
|
+
:context => @context)
|
102
|
+
end
|
103
|
+
|
104
|
+
def ensure_tables
|
105
|
+
create_tables
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -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
|
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,12 +16,35 @@
|
|
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
|
|
19
|
+
require 'digest/md5'
|
18
20
|
require 'yaml'
|
19
21
|
require 'racknga/cache_database'
|
20
22
|
|
21
23
|
module Racknga
|
22
24
|
module Middleware
|
25
|
+
class PerUserAgentCache
|
26
|
+
def initialize(application)
|
27
|
+
@application = application
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(environment)
|
31
|
+
mobile = environment["rack.jpmobile"]
|
32
|
+
if mobile
|
33
|
+
last_component = mobile.class.name.split(/::/).last
|
34
|
+
user_agent_key = "mobile:#{last_component.downcase}"
|
35
|
+
else
|
36
|
+
user_agent_key = "pc"
|
37
|
+
end
|
38
|
+
key = environment[Cache::KEY_KEY]
|
39
|
+
environment[Cache::KEY_KEY] = [key, user_agent_key].join(":")
|
40
|
+
@application.call(environment)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
23
44
|
class Cache
|
45
|
+
KEY_KEY = "racknga.cache.key"
|
46
|
+
START_TIME_KEY = "racknga.cache.start_time"
|
47
|
+
|
24
48
|
def initialize(application, options={})
|
25
49
|
@application = application
|
26
50
|
@options = Utils.normalize_options(options || {})
|
@@ -32,13 +56,15 @@ module Racknga
|
|
32
56
|
def call(environment)
|
33
57
|
request = Rack::Request.new(environment)
|
34
58
|
return @application.call(environment) unless use_cache?(request)
|
35
|
-
|
59
|
+
age = @database.configuration.age
|
60
|
+
key = environment[KEY_KEY] || request.fullpath
|
61
|
+
environment[START_TIME_KEY] = Time.now
|
36
62
|
cache = @database.responses
|
37
63
|
record = cache[key]
|
38
|
-
if record
|
39
|
-
handle_request_with_cache(cache, key, record, request)
|
64
|
+
if record and record.age == age
|
65
|
+
handle_request_with_cache(cache, key, age, record, request)
|
40
66
|
else
|
41
|
-
handle_request(cache, key, request)
|
67
|
+
handle_request(cache, key, age, request)
|
42
68
|
end
|
43
69
|
end
|
44
70
|
|
@@ -55,22 +81,8 @@ module Racknga
|
|
55
81
|
requeust.get? or requeust.head?
|
56
82
|
end
|
57
83
|
|
58
|
-
def key_prefix(request)
|
59
|
-
if request.respond_to?(:mobile?) and request.mobile?
|
60
|
-
last_component = request.mobile.class.name.split(/::/).last
|
61
|
-
"mobile:#{last_component.downcase}"
|
62
|
-
else
|
63
|
-
"pc"
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def normalize_path(path)
|
68
|
-
path.gsub(/&callback=jsonp\d+&_=\d+\z/, '')
|
69
|
-
end
|
70
|
-
|
71
84
|
def skip_caching_response?(status, headers, body)
|
72
85
|
return true if status != 200
|
73
|
-
return true if status != 200
|
74
86
|
|
75
87
|
headers = Rack::Utils::HeaderHash.new(headers)
|
76
88
|
content_type = headers["Content-Type"]
|
@@ -85,9 +97,10 @@ module Racknga
|
|
85
97
|
true
|
86
98
|
end
|
87
99
|
|
88
|
-
def handle_request(cache, key, request)
|
100
|
+
def handle_request(cache, key, age, request)
|
89
101
|
status, headers, body = @application.call(request.env)
|
90
102
|
if skip_caching_response?(status, headers, body)
|
103
|
+
log("skip", request)
|
91
104
|
return [status, headers, body]
|
92
105
|
end
|
93
106
|
|
@@ -99,23 +112,59 @@ module Racknga
|
|
99
112
|
stringified_body << data
|
100
113
|
end
|
101
114
|
headers = headers.to_hash
|
115
|
+
encoded_headers = headers.to_yaml
|
116
|
+
encoded_body = stringified_body.force_encoding("ASCII-8BIT")
|
102
117
|
cache[key] = {
|
103
118
|
:status => status,
|
104
|
-
:headers =>
|
105
|
-
:body =>
|
119
|
+
:headers => encoded_headers,
|
120
|
+
:body => encoded_body,
|
121
|
+
:checksum => compute_checksum(status, encoded_headers, encoded_body),
|
122
|
+
:age => age,
|
106
123
|
:created_at => now,
|
107
124
|
}
|
108
125
|
body = [stringified_body]
|
126
|
+
log("store", request)
|
109
127
|
[status, headers, body]
|
110
128
|
end
|
111
129
|
|
112
|
-
def handle_request_with_cache(cache, key, record, request)
|
113
|
-
|
114
|
-
|
130
|
+
def handle_request_with_cache(cache, key, age, record, request)
|
131
|
+
status = record.status
|
132
|
+
headers = record.headers
|
133
|
+
body = record.body
|
134
|
+
checksum = record.checksum
|
135
|
+
unless valid_cache?(status, headers, body, checksum)
|
136
|
+
log("invalid", request)
|
137
|
+
return handle_request(cache, key, age, request)
|
138
|
+
end
|
139
|
+
|
140
|
+
log("hit", request)
|
141
|
+
[status, YAML.load(headers), [body]]
|
142
|
+
end
|
143
|
+
|
144
|
+
def compute_checksum(status, encoded_headers, encoded_body)
|
145
|
+
md5 = Digest::MD5.new
|
146
|
+
md5 << status.to_s
|
147
|
+
md5 << ":"
|
148
|
+
md5 << encoded_headers
|
149
|
+
md5 << ":"
|
150
|
+
md5 << encoded_body
|
151
|
+
md5.hexdigest.force_encoding("ASCII-8BIT")
|
152
|
+
end
|
153
|
+
|
154
|
+
def valid_cache?(status, encoded_headers, encoded_body, checksum)
|
155
|
+
return false if status.nil? or encoded_headers.nil? or encoded_body.nil?
|
156
|
+
return false if checksum.nil?
|
157
|
+
compute_checksum(status, encoded_headers, encoded_body) == checksum
|
158
|
+
end
|
115
159
|
|
116
|
-
|
117
|
-
|
118
|
-
|
160
|
+
def log(tag, request)
|
161
|
+
return unless Middleware.const_defined?(:Log)
|
162
|
+
env = request.env
|
163
|
+
logger = env[Middleware::Log::LOGGER_KEY]
|
164
|
+
return if logger.nil?
|
165
|
+
start_time = env[START_TIME_KEY]
|
166
|
+
runtime = Time.now - start_time
|
167
|
+
logger.log("cache-#{tag}", request.fullpath, :runtime => runtime)
|
119
168
|
end
|
120
169
|
end
|
121
170
|
end
|
@@ -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
|
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
|
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
|
@@ -0,0 +1,77 @@
|
|
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
|
+
module Racknga
|
20
|
+
module Middleware
|
21
|
+
class JSONP
|
22
|
+
def initialize(application)
|
23
|
+
@application = application
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(environment)
|
27
|
+
request = Rack::Request.new(environment)
|
28
|
+
callback = request["callback"]
|
29
|
+
update_cache_key(request) if callback
|
30
|
+
status, headers, body = @application.call(environment)
|
31
|
+
return [status, headers, body] unless callback
|
32
|
+
return [status, headers, body] unless json_response?(headers)
|
33
|
+
body = Writer.new(callback, body)
|
34
|
+
[status, headers, body]
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def update_cache_key(request)
|
39
|
+
return unless Middleware.const_defined?(:Cache)
|
40
|
+
cache_key_key = Cache::KEY_KEY
|
41
|
+
|
42
|
+
path = request.fullpath
|
43
|
+
path, parameters = path.split(/\?/, 2)
|
44
|
+
if parameters
|
45
|
+
parameters = parameters.split(/[&;]/).reject do |parameter|
|
46
|
+
key, value = parameter.split(/\=/, 2)
|
47
|
+
key == "callback" or (key == "_" and value = /\A\d+\z/)
|
48
|
+
end.join("&")
|
49
|
+
path << "?" << parameters unless parameters.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
key = request.env[cache_key_key]
|
53
|
+
request.env[cache_key_key] = [key, path].compact.join(":")
|
54
|
+
end
|
55
|
+
|
56
|
+
def json_response?(headers)
|
57
|
+
content_type = Rack::Utils::HeaderHash.new(headers)["Content-Type"]
|
58
|
+
content_type == "application/json" or
|
59
|
+
content_type == "application/javascript" or
|
60
|
+
content_type == "text/javascript"
|
61
|
+
end
|
62
|
+
|
63
|
+
class Writer
|
64
|
+
def initialize(callback, body)
|
65
|
+
@callback = callback
|
66
|
+
@body = body
|
67
|
+
end
|
68
|
+
|
69
|
+
def each(&block)
|
70
|
+
block.call("#{@callback}(")
|
71
|
+
@body.each(&block)
|
72
|
+
block.call(");")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|