racknga 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Kouhei Sutou <kou@clear-code.com>
data/NEWS.ja.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = お知らせ
2
+
3
+ == 0.9.0: 2010-07-04
4
+
5
+ * 最初のリリース!
data/NEWS.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = NEWS
2
+
3
+ == 0.9.0: 2010-07-04
4
+
5
+ * Initial release!
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>
@@ -0,0 +1,4 @@
1
+ <link rel="stylesheet" href="/ranguba.css" type="text/css" />
2
+ <link rel="shortcut icon" href="/favicon.ico" />
3
+ <link rel="icon" href="/favicon.png" />
4
+ <title><%= h(title) %> - ラングバ</title>
@@ -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