racknga 0.9.0
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/AUTHORS +1 -0
- data/NEWS.ja.rdoc +5 -0
- data/NEWS.rdoc +5 -0
- data/README.ja.rdoc +44 -0
- data/README.rdoc +44 -0
- data/Rakefile +189 -0
- data/html/footer.html.erb +28 -0
- data/html/head.html.erb +4 -0
- data/html/header.html.erb +17 -0
- data/lib/racknga/cache_database.rb +75 -0
- data/lib/racknga/exception_mail_notifier.rb +144 -0
- data/lib/racknga/middleware/cache.rb +122 -0
- data/lib/racknga/middleware/deflater.rb +41 -0
- data/lib/racknga/middleware/exception_notifier.rb +50 -0
- data/lib/racknga/utils.rb +39 -0
- data/lib/racknga/version.rb +20 -0
- data/lib/racknga.rb +25 -0
- data/license/LGPL +504 -0
- metadata +139 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Kouhei Sutou <kou@clear-code.com>
|
data/NEWS.rdoc
ADDED
data/README.ja.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= はじめに
|
2
|
+
|
3
|
+
== 名前
|
4
|
+
|
5
|
+
racknga(らくんが)
|
6
|
+
|
7
|
+
== 説明
|
8
|
+
|
9
|
+
rroongaの機能を利用したRackのミドルウェアを提供します。
|
10
|
+
|
11
|
+
* rroonga: http://groonga.rubyforge.org/
|
12
|
+
* Rack: http://rack.rubyforge.org/
|
13
|
+
|
14
|
+
== 作者
|
15
|
+
|
16
|
+
Kouhei Sutou:: <tt><kou@clear-code.com></tt>
|
17
|
+
|
18
|
+
== ライセンス
|
19
|
+
|
20
|
+
LGPL 2.1です。詳しくはlicense/LGPLを見てください。
|
21
|
+
|
22
|
+
== 依存ソフトウェア
|
23
|
+
|
24
|
+
* Ruby >= 1.9.1
|
25
|
+
* rroonga
|
26
|
+
* Rack
|
27
|
+
|
28
|
+
== インストール
|
29
|
+
|
30
|
+
% sudo gem install racknga
|
31
|
+
|
32
|
+
== ドキュメント
|
33
|
+
|
34
|
+
http://groonga.rubyforge.org/racknga/
|
35
|
+
|
36
|
+
== メーリングリスト
|
37
|
+
|
38
|
+
質問、要望、バグ報告などはgroongaのMLにお願いします。
|
39
|
+
|
40
|
+
http://lists.sourceforge.jp/mailman/listinfo/groonga-dev
|
41
|
+
|
42
|
+
== 感謝
|
43
|
+
|
44
|
+
* ...
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= README
|
2
|
+
|
3
|
+
== Name
|
4
|
+
|
5
|
+
racknga
|
6
|
+
|
7
|
+
== Description
|
8
|
+
|
9
|
+
Rack middlewares that uses rroonga features.
|
10
|
+
|
11
|
+
* rroonga: http://groonga.rubyforge.org/
|
12
|
+
* Rack: http://rack.rubyforge.org/
|
13
|
+
|
14
|
+
== Authors
|
15
|
+
|
16
|
+
Kouhei Sutou:: <tt><kou@clear-code.com></tt>
|
17
|
+
|
18
|
+
== License
|
19
|
+
|
20
|
+
LGPL 2.1. See license/LGPL for details.
|
21
|
+
|
22
|
+
== Dependencies
|
23
|
+
|
24
|
+
* Ruby >= 1.9.1
|
25
|
+
* rroonga
|
26
|
+
* Rack
|
27
|
+
|
28
|
+
== Install
|
29
|
+
|
30
|
+
% sudo gem install racknga
|
31
|
+
|
32
|
+
== Documents
|
33
|
+
|
34
|
+
Japanese only. Sorry.
|
35
|
+
|
36
|
+
http://groonga.rubyforge.org/racknga/
|
37
|
+
|
38
|
+
== Mailing list
|
39
|
+
|
40
|
+
http://rubyforge.org/mailman/listinfo/groonga-users-en
|
41
|
+
|
42
|
+
== Thanks
|
43
|
+
|
44
|
+
* ...
|
data/Rakefile
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# -*- coding: utf-8; mode: ruby -*-
|
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 version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
require 'English'
|
19
|
+
|
20
|
+
require 'find'
|
21
|
+
require 'fileutils'
|
22
|
+
require 'pathname'
|
23
|
+
require 'erb'
|
24
|
+
require 'rubygems'
|
25
|
+
gem 'rdoc'
|
26
|
+
require 'hoe'
|
27
|
+
|
28
|
+
ENV["NODOT"] = "yes"
|
29
|
+
|
30
|
+
base_dir = Pathname(__FILE__).dirname
|
31
|
+
truncate_base_dir = Proc.new do |path|
|
32
|
+
path.relative_path_from(base_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
racknga_lib_dir = base_dir + 'lib'
|
36
|
+
$LOAD_PATH.unshift(racknga_lib_dir.to_s)
|
37
|
+
|
38
|
+
def guess_version
|
39
|
+
require 'racknga/version'
|
40
|
+
Racknga::VERSION
|
41
|
+
end
|
42
|
+
|
43
|
+
manifest = base_dir + "Manifest.txt"
|
44
|
+
manifest_contents = []
|
45
|
+
base_dir_included_components = %w(AUTHORS Rakefile
|
46
|
+
README.rdoc README.ja.rdoc
|
47
|
+
NEWS.rdoc NEWS.ja.rdoc
|
48
|
+
rroonga-build.rb extconf.rb pkg-config.rb)
|
49
|
+
excluded_components = %w(.cvsignore .gdb_history CVS depend Makefile doc pkg
|
50
|
+
.svn .git doc data .test-result tmp)
|
51
|
+
excluded_suffixes = %w(.png .ps .pdf .o .so .a .txt .~)
|
52
|
+
unless ENV["RUBY_CC_VERSION"]
|
53
|
+
excluded_components << "vendor"
|
54
|
+
end
|
55
|
+
base_dir.find do |target|
|
56
|
+
target = truncate_base_dir[target]
|
57
|
+
components = target.to_s.split(File::SEPARATOR)
|
58
|
+
if components.size == 1 and !target.directory?
|
59
|
+
next unless base_dir_included_components.include?(components[0])
|
60
|
+
end
|
61
|
+
Find.prune if (excluded_components - components) != excluded_components
|
62
|
+
next if excluded_suffixes.include?(File.extname(target))
|
63
|
+
manifest_contents << target if File.file?(target)
|
64
|
+
end
|
65
|
+
|
66
|
+
File.open(manifest, "w") do |f|
|
67
|
+
f.puts manifest_contents.sort.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
# For Hoe's no user friendly default behavior. :<
|
71
|
+
File.open("README.txt", "w") {|file| file << "= Dummy README\n== XXX\n"}
|
72
|
+
FileUtils.cp("NEWS.rdoc", "History.txt")
|
73
|
+
at_exit do
|
74
|
+
FileUtils.rm_f("README.txt")
|
75
|
+
FileUtils.rm_f("History.txt")
|
76
|
+
FileUtils.rm_f(manifest)
|
77
|
+
end
|
78
|
+
|
79
|
+
def cleanup_white_space(entry)
|
80
|
+
entry.gsub(/(\A\n+|\n+\z)/, '') + "\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
ENV["VERSION"] ||= guess_version
|
84
|
+
version = ENV["VERSION"]
|
85
|
+
project = nil
|
86
|
+
Hoe.spec('racknga') do
|
87
|
+
Hoe::Test::SUPPORTED_TEST_FRAMEWORKS[:testunit2] = "test/run-test.rb"
|
88
|
+
project = self
|
89
|
+
project.version = version.dup
|
90
|
+
project.rubyforge_name = 'groonga'
|
91
|
+
authors = File.join(base_dir, "AUTHORS")
|
92
|
+
project.author = File.readlines(authors).collect do |line|
|
93
|
+
if /\s*<[^<>]*>$/ =~ line
|
94
|
+
$PREMATCH
|
95
|
+
else
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end.compact
|
99
|
+
project.email = ['groonga-users-en@rubyforge.org',
|
100
|
+
'groonga-dev@lists.sourceforge.jp']
|
101
|
+
project.url = 'http://groonga.rubyforge.org/'
|
102
|
+
project.testlib = :testunit2
|
103
|
+
project.test_globs = ["test/run-test.rb"]
|
104
|
+
project.extra_deps += [["rroonga"],
|
105
|
+
["rack"]]
|
106
|
+
project.spec_extras = {
|
107
|
+
:extra_rdoc_files => Dir.glob("**/*.rdoc"),
|
108
|
+
:licenses => ["LGPL 2.1"]
|
109
|
+
}
|
110
|
+
project.readme_file = "README.ja.rdoc"
|
111
|
+
|
112
|
+
news_of_current_release = File.read("NEWS.rdoc").split(/^==\s.*$/)[1]
|
113
|
+
project.changes = cleanup_white_space(news_of_current_release)
|
114
|
+
|
115
|
+
entries = File.read("README.rdoc").split(/^==\s(.*)$/)
|
116
|
+
description = cleanup_white_space(entries[entries.index("Description") + 1])
|
117
|
+
project.summary, project.description, = description.split(/\n\n+/, 3)
|
118
|
+
|
119
|
+
project.remote_rdoc_dir = "racknga"
|
120
|
+
end
|
121
|
+
|
122
|
+
ObjectSpace.each_object(Rake::RDocTask) do |rdoc_task|
|
123
|
+
options = rdoc_task.options
|
124
|
+
t_option_index = options.index("--title") || options.index("-t")
|
125
|
+
rdoc_task.options[t_option_index, 2] = [] if t_option_index
|
126
|
+
rdoc_task.title = "racknga - #{version}"
|
127
|
+
end
|
128
|
+
|
129
|
+
task :publish_docs => [:prepare_docs_for_publishing]
|
130
|
+
|
131
|
+
|
132
|
+
include ERB::Util
|
133
|
+
|
134
|
+
def apply_template(file, head, header, footer)
|
135
|
+
content = File.read(file)
|
136
|
+
content = content.sub(/lang="en"/, 'lang="ja"')
|
137
|
+
|
138
|
+
title = nil
|
139
|
+
content = content.sub(/<title>(.+?)<\/title>/) do
|
140
|
+
title = $1
|
141
|
+
head.result(binding)
|
142
|
+
end
|
143
|
+
|
144
|
+
content = content.sub(/<body(?:.*?)>/) do |body_start|
|
145
|
+
"#{body_start}\n#{header.result(binding)}\n"
|
146
|
+
end
|
147
|
+
|
148
|
+
content = content.sub(/<\/body/) do |body_end|
|
149
|
+
"\n#{footer.result(binding)}\n#{body_end}"
|
150
|
+
end
|
151
|
+
|
152
|
+
File.open(file, "w") do |file|
|
153
|
+
file.print(content)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def erb_template(name)
|
158
|
+
file = File.join("html", "#{name}.html.erb")
|
159
|
+
template = File.read(file)
|
160
|
+
erb = ERB.new(template, nil, "-")
|
161
|
+
erb.filename = file
|
162
|
+
erb
|
163
|
+
end
|
164
|
+
|
165
|
+
task :prepare_docs_for_publishing do
|
166
|
+
head = erb_template("head")
|
167
|
+
header = erb_template("header")
|
168
|
+
footer = erb_template("footer")
|
169
|
+
Find.find("doc") do |file|
|
170
|
+
if /\.html\z/ =~ file and /_(?:c|rb)\.html\z/ !~ file
|
171
|
+
apply_template(file, head, header, footer)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
task :publish_html do
|
177
|
+
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
|
178
|
+
host = "#{config["username"]}@rubyforge.org"
|
179
|
+
|
180
|
+
rsync_args = "-av --exclude '*.erb' --exclude '*.svg' --exclude .svn"
|
181
|
+
remote_dir = "/var/www/gforge-projects/#{project.rubyforge_name}/"
|
182
|
+
sh "rsync #{rsync_args} html/ #{host}:#{remote_dir}"
|
183
|
+
end
|
184
|
+
|
185
|
+
task :tag do
|
186
|
+
sh("git tag -a #{version} -m 'release #{version}!!!'")
|
187
|
+
end
|
188
|
+
|
189
|
+
task(:release).prerequisites.reject! {|name| name == "clean"}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
</div>
|
2
|
+
|
3
|
+
<div class="sponsors">
|
4
|
+
<p id="sponsor-rubyforge">
|
5
|
+
<a href="http://rubyforge.org/projects/groonga/">
|
6
|
+
<img src="/rubyforge.png" width="120" height="24" border="0" alt="Ruby/groongaプロジェクトはRubyForge.orgにホスティングしてもらっています。" />
|
7
|
+
</a>
|
8
|
+
</p>
|
9
|
+
<p id="sponsor-tango">
|
10
|
+
<a href="http://tango.freedesktop.org/">
|
11
|
+
<img width="120" height="53" border="0" alt="Tango Desktop Projectのアイコンを利用しています。" src="/tango-logo.png" />
|
12
|
+
</a>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
<!-- Piwik -->
|
16
|
+
<a href="http://piwik.org" title="Web analytics" onclick="window.open(this.href);return(false);">
|
17
|
+
<script type="text/javascript">
|
18
|
+
var pkBaseURL = (("https:" == document.location.protocol) ? "https://www.clear-code.com/piwik/" : "http://www.clear-code.com/piwik/");
|
19
|
+
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
|
20
|
+
</script><script type="text/javascript">
|
21
|
+
piwik_action_name = '';
|
22
|
+
piwik_idsite = 2;
|
23
|
+
piwik_url = pkBaseURL + "piwik.php";
|
24
|
+
piwik_log(piwik_action_name, piwik_idsite, piwik_url);
|
25
|
+
</script>
|
26
|
+
<object><noscript><p>Web analytics <img src="http://www.clear-code.com/piwik/piwik.php?idsite=2" style="border:0" alt=""/></p></noscript></object></a>
|
27
|
+
<!-- End Piwik Tag -->
|
28
|
+
</div>
|
data/html/head.html.erb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="header">
|
2
|
+
<div class="title">
|
3
|
+
<a href="/">
|
4
|
+
<span class="title"><%= h(title) %></span>
|
5
|
+
<span class="title-separator">-</span>
|
6
|
+
<span class="title-project">ラングバ</span>
|
7
|
+
</a>
|
8
|
+
</div>
|
9
|
+
<ul class="menu">
|
10
|
+
<li id="menu-reference"><a href="/rroonga/">リファレンスマニュアル</a></li>
|
11
|
+
<li id="menu-tutorial"><a href="/rroonga/text/TUTORIAL_ja_rdoc.html">チュートリアル</a></li>
|
12
|
+
<li id="menu-install"><a href="/#install-rroonga">インストール</a></li>
|
13
|
+
<li id="menu-developer"><a href="/developer.html">開発者向け情報</a></li>
|
14
|
+
</ul>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="content">
|
@@ -0,0 +1,75 @@
|
|
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 version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
module Racknga
|
19
|
+
class CacheDatabase
|
20
|
+
def initialize(database_path)
|
21
|
+
@database_path = database_path
|
22
|
+
@context = Groonga::Context.new(:encoding => :none)
|
23
|
+
ensure_database
|
24
|
+
end
|
25
|
+
|
26
|
+
def responses
|
27
|
+
@context["Responses"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def purge_old_responses(threshold_time_stamp=nil)
|
31
|
+
threshold_time_stamp ||= Time.now
|
32
|
+
responses.each do |response|
|
33
|
+
response.delete if response.created_at < threshold_time_stamp
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ensure_database
|
38
|
+
if File.exist?(@database_path)
|
39
|
+
@database = Groonga::Database.open(@database_path, :context => @context)
|
40
|
+
else
|
41
|
+
create_database
|
42
|
+
end
|
43
|
+
ensure_tables
|
44
|
+
end
|
45
|
+
|
46
|
+
def close_database
|
47
|
+
@database.close
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def create_responses_table
|
52
|
+
Groonga::Schema.define(:context => @context) do |schema|
|
53
|
+
schema.create_table("Responses",
|
54
|
+
:type => :hash,
|
55
|
+
:key_type => "ShortText") do |table|
|
56
|
+
table.uint32("status")
|
57
|
+
table.short_text("headers")
|
58
|
+
table.text("body")
|
59
|
+
table.time("created_at")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_database
|
65
|
+
FileUtils.mkdir_p(File.dirname(@database_path))
|
66
|
+
@database = Groonga::Database.create(:path => @database_path,
|
67
|
+
:context => @context)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_tables
|
71
|
+
return if responses
|
72
|
+
create_responses_table
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,144 @@
|
|
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 version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
require 'time'
|
19
|
+
require 'net/smtp'
|
20
|
+
require 'etc'
|
21
|
+
require 'socket'
|
22
|
+
|
23
|
+
require 'racknga/utils'
|
24
|
+
|
25
|
+
module Racknga
|
26
|
+
# Ruby 1.9 only. 1.8 isn't supported.
|
27
|
+
class ExceptionMailNotifier
|
28
|
+
def initialize(options)
|
29
|
+
@options = Utils.normalize_options(options || {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def notify(exception, environment)
|
33
|
+
host = @options[:host] || "localhost"
|
34
|
+
return if to.empty?
|
35
|
+
mail = format(exception, environment)
|
36
|
+
Net::SMTP.start(host, @options[:port]) do |smtp|
|
37
|
+
smtp.send_message(mail, from, *to)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def format(exception, environment)
|
43
|
+
header = format_header(exception, environment)
|
44
|
+
body = format_body(exception, environment)
|
45
|
+
mail = "#{header}\r\n#{body}"
|
46
|
+
mail.force_encoding("utf-8")
|
47
|
+
begin
|
48
|
+
mail = mail.encode(charset)
|
49
|
+
rescue EncodingError
|
50
|
+
end
|
51
|
+
mail.force_encoding("ASCII-8BIT")
|
52
|
+
end
|
53
|
+
|
54
|
+
def format_header(exception, environment)
|
55
|
+
<<-EOH
|
56
|
+
MIME-Version: 1.0
|
57
|
+
Content-Type: Text/Plain; charset=#{charset}
|
58
|
+
Content-Transfer-Encoding: #{transfer_encoding}
|
59
|
+
From: #{from}
|
60
|
+
To: #{to.join(', ')}
|
61
|
+
Subject: #{encode_subject(subject(exception, environment))}
|
62
|
+
Date: #{Time.now.rfc2822}
|
63
|
+
EOH
|
64
|
+
end
|
65
|
+
|
66
|
+
def format_body(exception, environment)
|
67
|
+
request = Rack::Request.new(environment)
|
68
|
+
body = <<-EOB
|
69
|
+
URL: #{request.url}
|
70
|
+
--
|
71
|
+
#{exception.class}: #{exception}
|
72
|
+
--
|
73
|
+
#{exception.backtrace.join("\n")}
|
74
|
+
EOB
|
75
|
+
params = request.params
|
76
|
+
max_key_size = (environment.keys.collect(&:size) +
|
77
|
+
params.keys.collect(&:size)).max
|
78
|
+
body << <<-EOE
|
79
|
+
--
|
80
|
+
Environments:
|
81
|
+
EOE
|
82
|
+
environment.sort_by {|key, value| key}.each do |key, value|
|
83
|
+
body << " %*s: <%s>\n" % [max_key_size, key, value]
|
84
|
+
end
|
85
|
+
|
86
|
+
unless params.empty?
|
87
|
+
body << <<-EOE
|
88
|
+
--
|
89
|
+
Parameters:
|
90
|
+
EOE
|
91
|
+
params.sort_by {|key, value| key}.each do |key, value|
|
92
|
+
body << " %#{max_key_size}s: <%s>\n" % [key, value]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
body
|
97
|
+
end
|
98
|
+
|
99
|
+
def subject(exception, environment)
|
100
|
+
[@options[:subject_label], exception.to_s].compact.join(' ')
|
101
|
+
end
|
102
|
+
|
103
|
+
def to
|
104
|
+
@to ||= ensure_array(@options[:to]) || []
|
105
|
+
end
|
106
|
+
|
107
|
+
def ensure_array(maybe_array)
|
108
|
+
maybe_array = [maybe_array] if maybe_array.is_a?(String)
|
109
|
+
maybe_array
|
110
|
+
end
|
111
|
+
|
112
|
+
def from
|
113
|
+
@from ||= @options[:from] || guess_from
|
114
|
+
end
|
115
|
+
|
116
|
+
def guess_from
|
117
|
+
name = Etc.getpwuid(Process.uid).name
|
118
|
+
host = Socket.gethostname
|
119
|
+
"#{name}@#{host}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def charset
|
123
|
+
@options[:charset] || 'utf-8'
|
124
|
+
end
|
125
|
+
|
126
|
+
def transfer_encoding
|
127
|
+
case charset
|
128
|
+
when /\Autf-8\z/i
|
129
|
+
"8bit"
|
130
|
+
else
|
131
|
+
"7bit"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def encode_subject(subject)
|
136
|
+
case charset
|
137
|
+
when /\Aiso-2022-jp\z/i
|
138
|
+
NKF.nkf('-Wj -M', subject)
|
139
|
+
else
|
140
|
+
NKF.nkf('-Ww -M', subject)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,122 @@
|
|
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 version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
require 'yaml'
|
19
|
+
require 'racknga/cache_database'
|
20
|
+
|
21
|
+
module Racknga
|
22
|
+
module Middleware
|
23
|
+
class Cache
|
24
|
+
def initialize(application, options={})
|
25
|
+
@application = application
|
26
|
+
@options = Utils.normalize_options(options || {})
|
27
|
+
database_path = @options[:database_path]
|
28
|
+
raise ArgumentError, ":database_path is missing" if database_path.nil?
|
29
|
+
@database = CacheDatabase.new(database_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(environment)
|
33
|
+
request = Rack::Request.new(environment)
|
34
|
+
return @application.call(environment) unless use_cache?(request)
|
35
|
+
key = "#{key_prefix(request)}:#{normalize_path(request.fullpath)}"
|
36
|
+
cache = @database.responses
|
37
|
+
record = cache[key]
|
38
|
+
if record
|
39
|
+
handle_request_with_cache(cache, key, record, request)
|
40
|
+
else
|
41
|
+
handle_request(cache, key, request)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ensure_database
|
46
|
+
@database.ensure_database
|
47
|
+
end
|
48
|
+
|
49
|
+
def close_database
|
50
|
+
@database.close_database
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def use_cache?(requeust)
|
55
|
+
requeust.get? or requeust.head?
|
56
|
+
end
|
57
|
+
|
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
|
+
def skip_caching_response?(status, headers, body)
|
72
|
+
return true if status != 200
|
73
|
+
return true if status != 200
|
74
|
+
|
75
|
+
headers = Rack::Utils::HeaderHash.new(headers)
|
76
|
+
content_type = headers["Content-Type"]
|
77
|
+
if /\A(\w+)\/([\w.+\-]+)\b/ =~ content_type
|
78
|
+
media_type = $1
|
79
|
+
sub_type = $2
|
80
|
+
return false if media_type == "text"
|
81
|
+
return false if sub_type == "json"
|
82
|
+
return false if sub_type == "xml"
|
83
|
+
return false if /\+xml\z/ =~ sub_type
|
84
|
+
end
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_request(cache, key, request)
|
89
|
+
status, headers, body = @application.call(request.env)
|
90
|
+
if skip_caching_response?(status, headers, body)
|
91
|
+
return [status, headers, body]
|
92
|
+
end
|
93
|
+
|
94
|
+
now = Time.now
|
95
|
+
headers = Rack::Utils::HeaderHash.new(headers)
|
96
|
+
headers["Last-Modified"] ||= now.httpdate
|
97
|
+
stringified_body = ''
|
98
|
+
body.each do |data|
|
99
|
+
stringified_body << data
|
100
|
+
end
|
101
|
+
headers = headers.to_hash
|
102
|
+
cache[key] = {
|
103
|
+
:status => status,
|
104
|
+
:headers => headers.to_yaml,
|
105
|
+
:body => stringified_body.force_encoding("ASCII-8BIT"),
|
106
|
+
:created_at => now,
|
107
|
+
}
|
108
|
+
body = [stringified_body]
|
109
|
+
[status, headers, body]
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_request_with_cache(cache, key, record, request)
|
113
|
+
body = record["body"]
|
114
|
+
return handle_request(cache, key, request) if body.nil?
|
115
|
+
|
116
|
+
status = record["status"]
|
117
|
+
headers = YAML.load(record["headers"])
|
118
|
+
[status, headers, [body]]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|