benschwarz-merb-cache 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +224 -0
- data/Rakefile +17 -0
- data/lib/merb-cache.rb +15 -0
- data/lib/merb-cache/cache.rb +91 -0
- data/lib/merb-cache/cache_request.rb +48 -0
- data/lib/merb-cache/core_ext/enumerable.rb +9 -0
- data/lib/merb-cache/core_ext/hash.rb +21 -0
- data/lib/merb-cache/merb_ext/controller/class_methods.rb +244 -0
- data/lib/merb-cache/merb_ext/controller/instance_methods.rb +163 -0
- data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
- data/lib/merb-cache/stores/fundamental/file_store.rb +113 -0
- data/lib/merb-cache/stores/fundamental/memcached_store.rb +110 -0
- data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +119 -0
- data/lib/merb-cache/stores/strategy/action_store.rb +61 -0
- data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
- data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
- data/lib/merb-cache/stores/strategy/mintcache_store.rb +75 -0
- data/lib/merb-cache/stores/strategy/page_store.rb +68 -0
- data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
- data/spec/merb-cache/cache_request_spec.rb +78 -0
- data/spec/merb-cache/cache_spec.rb +88 -0
- data/spec/merb-cache/core_ext/enumerable_spec.rb +26 -0
- data/spec/merb-cache/core_ext/hash_spec.rb +51 -0
- data/spec/merb-cache/merb_ext/controller_spec.rb +5 -0
- data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +118 -0
- data/spec/merb-cache/stores/fundamental/file_store_spec.rb +205 -0
- data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +258 -0
- data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
- data/spec/merb-cache/stores/strategy/action_store_spec.rb +208 -0
- data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +227 -0
- data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +68 -0
- data/spec/merb-cache/stores/strategy/mintcache_store_spec.rb +59 -0
- data/spec/merb-cache/stores/strategy/page_store_spec.rb +146 -0
- data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +84 -0
- data/spec/spec_helper.rb +95 -0
- metadata +112 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Ben Burkert, Ben Schwarz, Daniel Neighman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
merb-cache
|
2
|
+
==========
|
3
|
+
|
4
|
+
A plugin for the Merb framework that provides caching stores,
|
5
|
+
strategies and helpers.
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
Tutorial
|
10
|
+
==========
|
11
|
+
|
12
|
+
Stores usually set up in application init file
|
13
|
+
(init.rb) or environment specific init file (so you can
|
14
|
+
use different stores for production, staging and development
|
15
|
+
environment if you need to).
|
16
|
+
|
17
|
+
# create a fundamental memcache store named :memcached for localhost
|
18
|
+
|
19
|
+
Merb::Cache.setup do
|
20
|
+
register(:memcached, MemcachedStore, :namespace => "my_app", :servers => ["127.0.0.1:11211"])
|
21
|
+
end
|
22
|
+
|
23
|
+
# a default FileStore
|
24
|
+
Merb::Cache.setup do
|
25
|
+
register(FileStore)
|
26
|
+
end
|
27
|
+
|
28
|
+
# another FileStore
|
29
|
+
Merb::Cache.setup do
|
30
|
+
register(:tmp_cache, FileStore, :dir => "/tmp")
|
31
|
+
end
|
32
|
+
|
33
|
+
Now lets see how we can use stores in the application:
|
34
|
+
|
35
|
+
class Tag
|
36
|
+
def find(parameters = {})
|
37
|
+
# poor man's identity map
|
38
|
+
|
39
|
+
if Merb::Cache[:memcached].exists?("tags", parameters)
|
40
|
+
Merb::Cache[:memcached].read("tags", parameters)
|
41
|
+
else
|
42
|
+
results = super(parameters)
|
43
|
+
Merb::Cache[:memcached].write("tags", results, parameters)
|
44
|
+
|
45
|
+
results
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def popularity_rating
|
50
|
+
# lets keep the popularity rating cached for 30 seconds
|
51
|
+
# merb-cache will create a key from the model's id & the interval parameter
|
52
|
+
|
53
|
+
Merb::Cache[:memcached].fetch(self.id, :interval => Time.now.to_i / 30) do
|
54
|
+
self.run_long_popularity_rating_query
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
Or, if you want to use memcache’s built in expire option:
|
61
|
+
|
62
|
+
# expire a cache entry for "bar" (identified by the key "foo" and
|
63
|
+
# parameters {:baz => :bay}) in two hours
|
64
|
+
Merb::Cache[:memcached].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
65
|
+
|
66
|
+
# this will fail, because FileStore cannot expire cache entries
|
67
|
+
Merb::Cache[:default].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
68
|
+
|
69
|
+
# writing to the FileStore will fail, but the MemcachedStore will succeed
|
70
|
+
Merb::Cache[:default, :memcached].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
71
|
+
|
72
|
+
# this will fail
|
73
|
+
Merb::Cache[:default, :memcached].write_all("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
74
|
+
|
75
|
+
|
76
|
+
Setting up strategy stores is very similar to fundamental stores:
|
77
|
+
|
78
|
+
Merb::Cache.setup do
|
79
|
+
|
80
|
+
# wraps the :memcached store we setup earlier
|
81
|
+
register(:zipped, GzipStore[:memcached])
|
82
|
+
|
83
|
+
# wrap a strategy store
|
84
|
+
register(:sha_and_zip, SHA1Store[:zipped])
|
85
|
+
|
86
|
+
# you can even use unnamed fundamental stores
|
87
|
+
register(:zipped_images, GzipStore[FileStore],
|
88
|
+
:dir => Merb.root / "public" / "images")
|
89
|
+
|
90
|
+
# or a combination or strategy & fundamental stores
|
91
|
+
register(:secured, SHA1Store[GzipStore[FileStore], FileStore],
|
92
|
+
:dir => Merb.root / "private")
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
You can use these strategy stores exactly like fundamental stores in your app code.
|
97
|
+
|
98
|
+
Action & Page Caching
|
99
|
+
Action & page caching have been implemented in strategy stores. So instead of manually specifying which type of caching you want for each action, you simply ask merb-cache to cache your action, and it will use the fastest cache available.
|
100
|
+
|
101
|
+
First, let’s setup our page & action stores:
|
102
|
+
|
103
|
+
config/environments/development.rb
|
104
|
+
|
105
|
+
Merb::Cache.setup do
|
106
|
+
|
107
|
+
# the order that stores are setup is important
|
108
|
+
# faster stores should be setup first
|
109
|
+
|
110
|
+
# page cache to the public dir
|
111
|
+
register(:page_store, PageStore[FileStore],
|
112
|
+
:dir => Merb.root / "public")
|
113
|
+
|
114
|
+
# action cache to memcache
|
115
|
+
register(:action_store, ActionStore[:sha_and_zip])
|
116
|
+
|
117
|
+
# sets up the ordering of stores when attempting to read/write cache entries
|
118
|
+
register(:default, AdhocStore[:page_store, :action_store])
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
And now in our controller:
|
123
|
+
class Tags < Merb::Controller
|
124
|
+
|
125
|
+
# index & show will be page cached to the public dir. The index
|
126
|
+
# action has no parameters, and the show parameter's are part of
|
127
|
+
# the URL, making them both page-cache'able
|
128
|
+
cache :index, :show
|
129
|
+
|
130
|
+
def index
|
131
|
+
render
|
132
|
+
end
|
133
|
+
|
134
|
+
def show(:slug)
|
135
|
+
display Tag.first(:slug => slug)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
Our controller now page caches but the index & show action. Furthermore,
|
140
|
+
the show action is cached separately for each slug parameter automatically.
|
141
|
+
|
142
|
+
|
143
|
+
class Tags < Merb::Controller
|
144
|
+
|
145
|
+
# the term is a route param, while the page & per_page params are part of the query string.
|
146
|
+
# If only the term param is supplied, the request can be page cached, but if the page and/or
|
147
|
+
# per_page param is part of the query string, the request will action cache.
|
148
|
+
cache :catalog
|
149
|
+
|
150
|
+
def catalog(term = 'a', page = 1, per_page = 20)
|
151
|
+
@tags = Tag.for_term(term).paginate(page, per_page)
|
152
|
+
|
153
|
+
display @tags
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
Because the specific type of caching is not specified, the same action can either
|
158
|
+
be page cached or action cached depending on the context of the request.
|
159
|
+
|
160
|
+
|
161
|
+
Keeping a “Hot” Cache
|
162
|
+
=====================
|
163
|
+
|
164
|
+
Cache expiration is a constant problem for developers. When should content
|
165
|
+
be expired? Should we “sweep” stale content? How do we balance serving fresh
|
166
|
+
content and maintaining fast response times? These are difficult questions
|
167
|
+
for developers, and are usually answered with ugly code added across our
|
168
|
+
models, views, and controllers. Instead of designing an elaborate
|
169
|
+
caching and expiring system, an alternate approach is to keep a “hot” cache.
|
170
|
+
|
171
|
+
So what is a “hot” cache? A hot cache is what you get when you ignore
|
172
|
+
trying to manually expire content, and instead focus on replacing old
|
173
|
+
content with fresh data as soon as it becomes stale. Keeping a hot
|
174
|
+
cache means no difficult expiration logic spread out across your
|
175
|
+
app, and will all but eliminate cache misses.
|
176
|
+
|
177
|
+
The problem until now with this approach has been the impact on
|
178
|
+
response times. If the request has to wait on any pages that
|
179
|
+
it has made stale to render the fresh version, it can slow down
|
180
|
+
the response time dramatically. Thankfully, Merb has the run_later
|
181
|
+
method which allows the fresh content to render after the
|
182
|
+
response has been sent to the browser.
|
183
|
+
It’s the best of both worlds. Here’s an example.
|
184
|
+
|
185
|
+
|
186
|
+
class Tags < Merb::Controller
|
187
|
+
|
188
|
+
cache :index
|
189
|
+
eager_cache :create, :index
|
190
|
+
|
191
|
+
def index
|
192
|
+
display Tag.all
|
193
|
+
end
|
194
|
+
|
195
|
+
def create(slug)
|
196
|
+
@tag = Tag.new(slug)
|
197
|
+
|
198
|
+
# redirect them back to the index action
|
199
|
+
redirect url(:tags)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
The controller will eager_cache the index action whenever the create action
|
204
|
+
is successfully called. If the client were to post a new tag to the
|
205
|
+
create action, they would be redirect back to the index action.
|
206
|
+
Right after the response had been sent to the client, the index action
|
207
|
+
would be rendered with the newly created tag included and replaced
|
208
|
+
in the cache. So when the user requests for the index action gets
|
209
|
+
to the server, the freshest version is already in the cache, and
|
210
|
+
the cache miss is avoided. This works regardless of the way
|
211
|
+
the index action is cached.
|
212
|
+
|
213
|
+
Hot cache helps fight dog pile effect
|
214
|
+
(http://highscalability.com/strategy-break-memcache-dog-pile) but
|
215
|
+
should be used with caution. It's great when you want to eagerly cache
|
216
|
+
some page that user is not going to see immediately after
|
217
|
+
creating/updating something because hot cache in current implementation
|
218
|
+
uses worker queue (knows as run_later) and it does not guarantee that
|
219
|
+
before redirect hits the action data is gonna be already cached.
|
220
|
+
|
221
|
+
A good use case of eager caching is front end page of
|
222
|
+
some newspaper site when staff updates site content, and
|
223
|
+
is not redirected to page that uses new cache values immediately,
|
224
|
+
but other users access it frequently.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run all examples (or a specific spec with TASK=xxxx)"
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.spec_opts = ["-cfs"]
|
7
|
+
t.spec_files = begin
|
8
|
+
if ENV["TASK"]
|
9
|
+
ENV["TASK"].split(',').map { |task| "spec/**/#{task}_spec.rb" }
|
10
|
+
else
|
11
|
+
FileList['spec/**/*_spec.rb']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Default: run spec examples'
|
17
|
+
task :default => 'spec'
|
data/lib/merb-cache.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# make sure we're running inside Merb
|
2
|
+
if defined?(Merb::Plugins)
|
3
|
+
require "merb-cache" / "cache"
|
4
|
+
require "merb-cache" / "core_ext" / "enumerable"
|
5
|
+
require "merb-cache" / "core_ext" / "hash"
|
6
|
+
require "merb-cache" / "merb_ext" / "controller" / "class_methods"
|
7
|
+
require "merb-cache" / "merb_ext" / "controller" / "instance_methods"
|
8
|
+
require "merb-cache" / "cache_request"
|
9
|
+
|
10
|
+
class Merb::Controller
|
11
|
+
extend Merb::Cache::Controller::ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
Merb::Controller.send(:include, Merb::Cache::Controller::InstanceMethods)
|
15
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Merb
|
2
|
+
# A convinient way to get at Merb::Cache
|
3
|
+
def self.cache
|
4
|
+
Merb::Cache
|
5
|
+
end
|
6
|
+
|
7
|
+
module Cache
|
8
|
+
|
9
|
+
def self.setup(&blk)
|
10
|
+
if Merb::BootLoader.finished?(Merb::BootLoader::BeforeAppLoads)
|
11
|
+
instance_eval(&blk) unless blk.nil?
|
12
|
+
else
|
13
|
+
Merb::BootLoader.before_app_loads do
|
14
|
+
instance_eval(&blk) unless blk.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# autoload is used so that gem dependencies can be required only when needed by
|
20
|
+
# adding the require statement in the store file.
|
21
|
+
autoload :AbstractStore, "merb-cache" / "stores" / "fundamental" / "abstract_store"
|
22
|
+
autoload :FileStore, "merb-cache" / "stores" / "fundamental" / "file_store"
|
23
|
+
autoload :MemcachedStore, "merb-cache" / "stores" / "fundamental" / "memcached_store"
|
24
|
+
|
25
|
+
autoload :AbstractStrategyStore, "merb-cache" / "stores" / "strategy" / "abstract_strategy_store"
|
26
|
+
autoload :ActionStore, "merb-cache" / "stores" / "strategy" / "action_store"
|
27
|
+
autoload :AdhocStore, "merb-cache" / "stores" / "strategy" / "adhoc_store"
|
28
|
+
autoload :GzipStore, "merb-cache" / "stores" / "strategy" / "gzip_store"
|
29
|
+
autoload :PageStore, "merb-cache" / "stores" / "strategy" / "page_store"
|
30
|
+
autoload :SHA1Store, "merb-cache" / "stores" / "strategy" / "sha1_store"
|
31
|
+
autoload :MintCacheStore, "merb-cache" / "stores" / "strategy" / "mintcache_store"
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_accessor :stores
|
35
|
+
end
|
36
|
+
|
37
|
+
self.stores = {}
|
38
|
+
|
39
|
+
# Cache store lookup
|
40
|
+
# name<Symbol> : The name of a registered store
|
41
|
+
# Returns<Nil AbstractStore> : A thread-safe copy of the store
|
42
|
+
def self.[](*names)
|
43
|
+
names = names.first if names.first.is_a? Array
|
44
|
+
if names.size == 1
|
45
|
+
Thread.current[:'merb-cache'] ||= {}
|
46
|
+
(Thread.current[:'merb-cache'][names.first] ||= stores[names.first].clone)
|
47
|
+
else
|
48
|
+
AdhocStore[*names]
|
49
|
+
end
|
50
|
+
rescue TypeError
|
51
|
+
raise(StoreNotFound, "Could not find the :#{names.first} store")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Clones the cache stores for the current thread
|
55
|
+
def self.clone_stores
|
56
|
+
@stores.inject({}) {|h, (k, s)| h[k] = s.clone; h}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Registers the cache store name with a type & options
|
60
|
+
# name<Symbol> : An optional symbol to give the cache. :default is used if no name is given.
|
61
|
+
# klass<Class> : A store type.
|
62
|
+
# opts<Hash> : A hash to pass through to the store for configuration.
|
63
|
+
def self.register(name, klass = nil, opts = {})
|
64
|
+
klass, opts = nil, klass if klass.is_a? Hash
|
65
|
+
name, klass = default_store_name, name if klass.nil?
|
66
|
+
|
67
|
+
raise StoreExists, "#{name} store already setup" if @stores.has_key?(name)
|
68
|
+
|
69
|
+
@stores[name] = (AdhocStore === klass) ? klass : klass.new(opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Checks to see if a given store exists already.
|
73
|
+
def self.exists?(name)
|
74
|
+
return true if self[name]
|
75
|
+
rescue StoreNotFound
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
# Default store name is :default.
|
80
|
+
def self.default_store_name
|
81
|
+
:default
|
82
|
+
end
|
83
|
+
|
84
|
+
class NotSupportedError < Exception; end
|
85
|
+
|
86
|
+
class StoreExists < Exception; end
|
87
|
+
|
88
|
+
# Raised when requested store cannot be found on the list of registered.
|
89
|
+
class StoreNotFound < Exception; end
|
90
|
+
end #Cache
|
91
|
+
end #Merb
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Merb
|
2
|
+
module Cache
|
3
|
+
class CacheRequest < Merb::Request
|
4
|
+
|
5
|
+
attr_accessor :params
|
6
|
+
|
7
|
+
def initialize(uri = "", params = {}, env = {})
|
8
|
+
if uri || !env[Merb::Const::REQUEST_URI]
|
9
|
+
uri = URI(uri || '/')
|
10
|
+
env[Merb::Const::REQUEST_URI] = uri.respond_to?(:request_uri) ? uri.request_uri : uri.to_s
|
11
|
+
env[Merb::Const::HTTP_HOST] = uri.host + (uri.port != 80 ? ":#{uri.port}" : '') if uri.host
|
12
|
+
env[Merb::Const::SERVER_PORT] = uri.port.to_s if uri.port
|
13
|
+
env[Merb::Const::QUERY_STRING] = uri.query.to_s if uri.query
|
14
|
+
env[Merb::Const::REQUEST_PATH] = env[Merb::Const::PATH_INFO] = uri.path
|
15
|
+
end
|
16
|
+
|
17
|
+
env[Merb::Const::REQUEST_METHOD] = env[:method] ? env.delete(:method).to_s.upcase : 'GET'
|
18
|
+
|
19
|
+
super(DEFAULT_ENV.merge(env))
|
20
|
+
|
21
|
+
|
22
|
+
@params = params || {}
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULT_ENV = Mash.new({
|
26
|
+
'SERVER_NAME' => 'localhost',
|
27
|
+
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
|
28
|
+
'HTTP_USER_AGENT' => 'Ruby/Merb (ver: ' + Merb::VERSION + ') merb-cache',
|
29
|
+
'SCRIPT_NAME' => '/',
|
30
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
31
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0',
|
32
|
+
'HTTP_ACCEPT_LANGUAGE' => 'en,ja;q=0.9,fr;q=0.9,de;q=0.8,es;q=0.7,it;q=0.7,nl;q=0.6,sv;q=0.5,nb;q=0.5,da;q=0.4,fi;q=0.3,pt;q=0.3,zh-Hans;q=0.2,zh-Hant;q=0.1,ko;q=0.1',
|
33
|
+
'HTTP_HOST' => 'localhost',
|
34
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
35
|
+
'SERVER_SOFTWARE' => 'Mongrel 1.1',
|
36
|
+
'HTTP_KEEP_ALIVE' => '300',
|
37
|
+
'HTTP_REFERER' => 'http://localhost/',
|
38
|
+
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
39
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
40
|
+
'REQUEST_METHOD' => 'GET',
|
41
|
+
'SERVER_PORT' => '80',
|
42
|
+
'GATEWAY_INTERFACE' => 'CGI/1.2',
|
43
|
+
'HTTP_ACCEPT' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
44
|
+
'HTTP_CONNECTION' => 'keep-alive'
|
45
|
+
}) unless defined?(DEFAULT_ENV)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
|
5
|
+
def to_sha2
|
6
|
+
string = ""
|
7
|
+
keys.sort_by{|k| k.to_s}.each do |k|
|
8
|
+
string << "&#{k}="
|
9
|
+
case self[k]
|
10
|
+
when Array
|
11
|
+
string << self[k].join('|')
|
12
|
+
when Hash
|
13
|
+
string << self[k].to_sha2
|
14
|
+
else
|
15
|
+
string << self[k].to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
Digest::SHA2.hexdigest(string)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|