rocketio 0.0.0 → 0.0.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -5
- data/.pryrc +2 -0
- data/.travis.yml +3 -0
- data/README.md +22 -5
- data/Rakefile +7 -1
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/rocketio.rb +131 -3
- data/lib/rocketio/application.rb +31 -0
- data/lib/rocketio/controller.rb +288 -0
- data/lib/rocketio/controller/authentication.rb +141 -0
- data/lib/rocketio/controller/authorization.rb +53 -0
- data/lib/rocketio/controller/cookies.rb +59 -0
- data/lib/rocketio/controller/error_handlers.rb +89 -0
- data/lib/rocketio/controller/filters.rb +119 -0
- data/lib/rocketio/controller/flash.rb +21 -0
- data/lib/rocketio/controller/helpers.rb +438 -0
- data/lib/rocketio/controller/middleware.rb +32 -0
- data/lib/rocketio/controller/render.rb +148 -0
- data/lib/rocketio/controller/render/engine.rb +76 -0
- data/lib/rocketio/controller/render/layout.rb +27 -0
- data/lib/rocketio/controller/render/layouts.rb +85 -0
- data/lib/rocketio/controller/render/templates.rb +83 -0
- data/lib/rocketio/controller/request.rb +115 -0
- data/lib/rocketio/controller/response.rb +84 -0
- data/lib/rocketio/controller/sessions.rb +64 -0
- data/lib/rocketio/controller/token_auth.rb +118 -0
- data/lib/rocketio/controller/websocket.rb +21 -0
- data/lib/rocketio/error_templates/404.html +3 -0
- data/lib/rocketio/error_templates/409.html +7 -0
- data/lib/rocketio/error_templates/500.html +3 -0
- data/lib/rocketio/error_templates/501.html +6 -0
- data/lib/rocketio/error_templates/layout.html +1 -0
- data/lib/rocketio/exceptions.rb +4 -0
- data/lib/rocketio/router.rb +65 -0
- data/lib/rocketio/util.rb +122 -0
- data/lib/rocketio/version.rb +2 -2
- data/rocketio.gemspec +21 -17
- data/test/aliases_test.rb +54 -0
- data/test/authentication_test.rb +307 -0
- data/test/authorization_test.rb +91 -0
- data/test/cache_control_test.rb +268 -0
- data/test/content_type_test.rb +124 -0
- data/test/cookies_test.rb +49 -0
- data/test/error_handlers_test.rb +125 -0
- data/test/etag_test.rb +445 -0
- data/test/filters_test.rb +177 -0
- data/test/halt_test.rb +73 -0
- data/test/helpers_test.rb +171 -0
- data/test/middleware_test.rb +57 -0
- data/test/redirect_test.rb +135 -0
- data/test/render/engine_test.rb +71 -0
- data/test/render/get.erb +1 -0
- data/test/render/items.erb +1 -0
- data/test/render/layout.erb +1 -0
- data/test/render/layout_test.rb +104 -0
- data/test/render/layouts/master.erb +1 -0
- data/test/render/layouts_test.rb +145 -0
- data/test/render/master.erb +1 -0
- data/test/render/post.erb +1 -0
- data/test/render/put.erb +1 -0
- data/test/render/render_test.rb +101 -0
- data/test/render/setup.rb +14 -0
- data/test/render/templates/a/get.erb +1 -0
- data/test/render/templates/master.erb +1 -0
- data/test/render/templates_test.rb +146 -0
- data/test/request_test.rb +105 -0
- data/test/response_test.rb +119 -0
- data/test/routes_test.rb +70 -0
- data/test/sendfile_test.rb +209 -0
- data/test/sessions_test.rb +176 -0
- data/test/setup.rb +59 -0
- metadata +144 -9
- data/LICENSE.txt +0 -22
@@ -0,0 +1,21 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Flash
|
3
|
+
KEY_FORMAT = '__session__flash__%s'.freeze
|
4
|
+
|
5
|
+
def initialize session = {}
|
6
|
+
@session = session
|
7
|
+
end
|
8
|
+
|
9
|
+
def []= key, val
|
10
|
+
@session[key(key)] = val
|
11
|
+
end
|
12
|
+
|
13
|
+
def [] key
|
14
|
+
@session.delete(key(key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def key key
|
18
|
+
KEY_FORMAT % key.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,438 @@
|
|
1
|
+
# Copyright (c) 2007, 2008, 2009 Blake Mizerany
|
2
|
+
# Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
|
3
|
+
# Copyright (c) 2015 Slee Woo
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module RocketIO
|
27
|
+
class Controller
|
28
|
+
|
29
|
+
# helpers to determine actual request method
|
30
|
+
# `get?` returns true for GET, `post?` for POST etc.
|
31
|
+
RocketIO::REQUEST_METHODS.each_key do |request_method|
|
32
|
+
define_method request_method.downcase + '?' do
|
33
|
+
env[RocketIO::REQUEST_METHOD] == request_method
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# whether or not the status is set to 1xx
|
38
|
+
def informational?
|
39
|
+
response.status.between? 100, 199
|
40
|
+
end
|
41
|
+
|
42
|
+
# whether or not the status is set to 2xx
|
43
|
+
def success?
|
44
|
+
response.status.between? 200, 299
|
45
|
+
end
|
46
|
+
|
47
|
+
# whether or not the status is set to 3xx
|
48
|
+
def redirect?
|
49
|
+
response.status.between? 300, 399
|
50
|
+
end
|
51
|
+
|
52
|
+
# whether or not the status is set to 4xx
|
53
|
+
def client_error?
|
54
|
+
response.status.between? 400, 499
|
55
|
+
end
|
56
|
+
|
57
|
+
# whether or not the status is set to 5xx
|
58
|
+
def server_error?
|
59
|
+
response.status.between? 500, 599
|
60
|
+
end
|
61
|
+
|
62
|
+
# whether or not the status is set to 404
|
63
|
+
def not_found?
|
64
|
+
response.status == 404
|
65
|
+
end
|
66
|
+
|
67
|
+
# returns true for HTTP/1.1 requests
|
68
|
+
def http_1_1?
|
69
|
+
env[RocketIO::HTTP_VERSION] == RocketIO::HTTP_1_1
|
70
|
+
end
|
71
|
+
|
72
|
+
def xhr?
|
73
|
+
env[RocketIO::HTTP_X_REQUESTED_WITH] == RocketIO::XML_HTTP_REQUEST
|
74
|
+
end
|
75
|
+
|
76
|
+
# switch controller and halt with returned response.
|
77
|
+
# any arguments will be passed to requested method.
|
78
|
+
#
|
79
|
+
# @example pass control to User controller, call requested method without arguments
|
80
|
+
# pass User
|
81
|
+
#
|
82
|
+
# @example pass control to User controller, call requested method with [:bob, :bobsen] arguments
|
83
|
+
# pass User, :bob, :bobsen
|
84
|
+
#
|
85
|
+
def pass controller, *args
|
86
|
+
halt controller.initialize_controller(RocketIO::REQUEST_METHODS[request_method], args).call(env)
|
87
|
+
end
|
88
|
+
|
89
|
+
# stop executing any code and send response to browser.
|
90
|
+
#
|
91
|
+
# accepts an arbitrary number of arguments.
|
92
|
+
# if first argument is a Rack::Response,
|
93
|
+
# halting right away using the first arg as response and ignore other args.
|
94
|
+
#
|
95
|
+
# if first arg is a Array, updating current response
|
96
|
+
# using first array element as status, second to update headers and 3rd as body
|
97
|
+
#
|
98
|
+
# if some arg is an Integer, it will be used as status code.
|
99
|
+
# if some arg is a Hash, it is treated as headers.
|
100
|
+
# any other args are treated as body.
|
101
|
+
#
|
102
|
+
# if no args given it halts with current response.
|
103
|
+
#
|
104
|
+
# @example returning "Well Done" body with 200 status code
|
105
|
+
# halt 'Well Done'
|
106
|
+
#
|
107
|
+
# @example halting with current response without alter it in any way
|
108
|
+
# halt
|
109
|
+
#
|
110
|
+
# @example returning a error message with 500 code:
|
111
|
+
# halt 500, 'Sorry, some fatal error occurred'
|
112
|
+
#
|
113
|
+
# @example custom content type
|
114
|
+
# halt File.read('/path/to/theme.css'), 'Content-Type' => mime_type('.css')
|
115
|
+
#
|
116
|
+
# @example sending custom Rack response
|
117
|
+
# halt [200, {'Content-Disposition' => "attachment; filename=some-file"}, some_IO_instance]
|
118
|
+
#
|
119
|
+
# @param [Array] *args
|
120
|
+
#
|
121
|
+
def halt *args
|
122
|
+
args.each do |a|
|
123
|
+
case a
|
124
|
+
when Rack::Response
|
125
|
+
throw(:__response__, a.finish)
|
126
|
+
when Fixnum
|
127
|
+
response.status = a
|
128
|
+
when Array
|
129
|
+
if a.size == 3
|
130
|
+
response.status = a[0]
|
131
|
+
response.headers.update(a[1])
|
132
|
+
response.body = a[2]
|
133
|
+
break
|
134
|
+
else
|
135
|
+
response.body = a
|
136
|
+
end
|
137
|
+
when Hash
|
138
|
+
response.headers.update(a)
|
139
|
+
else
|
140
|
+
response.body = a
|
141
|
+
end
|
142
|
+
end
|
143
|
+
throw(:__response__, response.finish)
|
144
|
+
end
|
145
|
+
|
146
|
+
# @example
|
147
|
+
# flash[:alert] = 'some secret'
|
148
|
+
# p flash[:alert] #=> "some secret"
|
149
|
+
# p flash[:alert] #=> nil
|
150
|
+
def flash
|
151
|
+
@__flash_proxy__ ||= RocketIO::Flash.new(session)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Halt processing and redirect to the URI provided.
|
155
|
+
def redirect uri
|
156
|
+
response.status = http_1_1? && !get? ? 303 : 302
|
157
|
+
|
158
|
+
# According to RFC 2616 section 14.30, "the field value consists of a
|
159
|
+
# single absolute URI"
|
160
|
+
response[RocketIO::LOCATION] = uri(uri.to_s)
|
161
|
+
halt
|
162
|
+
end
|
163
|
+
|
164
|
+
def permanent_redirect uri
|
165
|
+
response.status = 301
|
166
|
+
response[RocketIO::LOCATION] = uri(uri.to_s)
|
167
|
+
halt
|
168
|
+
end
|
169
|
+
|
170
|
+
# Generates the absolute URI for a given path in the app.
|
171
|
+
# Takes Rack routers and reverse proxies into account.
|
172
|
+
def uri addr = nil, absolute = true, add_script_name = true
|
173
|
+
return addr if addr && addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
|
174
|
+
uri = [host = ""]
|
175
|
+
if absolute
|
176
|
+
host << "http#{'s' if request.secure?}://"
|
177
|
+
if request.forwarded? or request.port != (request.secure? ? 443 : 80)
|
178
|
+
host << request.host_with_port
|
179
|
+
else
|
180
|
+
host << request.host
|
181
|
+
end
|
182
|
+
end
|
183
|
+
uri << request.script_name.to_s if add_script_name
|
184
|
+
uri << (addr ? addr : request.path_info).to_s
|
185
|
+
File.join(uri)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set multiple response headers with Hash.
|
189
|
+
def headers hash = nil
|
190
|
+
response.headers.merge!(hash) if hash
|
191
|
+
response.headers
|
192
|
+
end
|
193
|
+
|
194
|
+
# shorthand for content_type(charset: 'something')
|
195
|
+
def charset charset
|
196
|
+
content_type(charset: charset)
|
197
|
+
end
|
198
|
+
|
199
|
+
# returns, set or update content type.
|
200
|
+
# if called without args it will return current content type.
|
201
|
+
# if called with a single argument, given argument will be set as content type.
|
202
|
+
# if a type and hash given it will set brand new content type composed of given type and opts.
|
203
|
+
# if only a hash given it will update current content type with given opts.
|
204
|
+
# if no content type is set it will use default one + given opts.
|
205
|
+
#
|
206
|
+
# @example set content type
|
207
|
+
# content_type '.json'
|
208
|
+
#
|
209
|
+
# @example set content type with some params
|
210
|
+
# content_type '.json', level: 1
|
211
|
+
#
|
212
|
+
# @example add params to current content type
|
213
|
+
# content_type comment: 'Boo!'
|
214
|
+
#
|
215
|
+
def content_type *args
|
216
|
+
return response[RocketIO::CONTENT_TYPE] if args.empty?
|
217
|
+
params = args.last.is_a?(Hash) ? args.pop.map {|kv| kv.map!(&:to_s)}.to_h : {}
|
218
|
+
default = params.delete('default')
|
219
|
+
|
220
|
+
if type = args.first
|
221
|
+
mime_type = mime_type(type) || default || raise(ArgumentError, "Unknown media type: %p" % type)
|
222
|
+
else
|
223
|
+
mime_type = response[RocketIO::CONTENT_TYPE]
|
224
|
+
end
|
225
|
+
mime_type ||= RocketIO::DEFAULT_CONTENT_TYPE
|
226
|
+
|
227
|
+
mime_type, _params = mime_type.split(';')
|
228
|
+
if _params
|
229
|
+
params = _params.split(',').map! {|o| o.strip.split('=')}.to_h.merge!(params)
|
230
|
+
end
|
231
|
+
|
232
|
+
if params.any?
|
233
|
+
mime_type << '; '
|
234
|
+
mime_type << params.map do |key, val|
|
235
|
+
val = val.inspect if val =~ /[";,]/
|
236
|
+
[key, val]*'='
|
237
|
+
end.join(', ')
|
238
|
+
end
|
239
|
+
response[RocketIO::CONTENT_TYPE] = mime_type
|
240
|
+
end
|
241
|
+
|
242
|
+
# Set the Content-Disposition to "attachment" with the specified filename,
|
243
|
+
# instructing the user agents to prompt to save.
|
244
|
+
def attachment(filename = nil, disposition = 'attachment')
|
245
|
+
response[RocketIO::CONTENT_DISPOSITION] = disposition.to_s
|
246
|
+
if filename
|
247
|
+
response[RocketIO::CONTENT_DISPOSITION] << ('; filename="%s"' % File.basename(filename))
|
248
|
+
ext = File.extname(filename)
|
249
|
+
content_type(ext) unless response[RocketIO::CONTENT_TYPE] or ext.empty?
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Use the contents of the file at +path+ as the response body.
|
254
|
+
def send_file path, opts = {}
|
255
|
+
if opts[:type] or not response[RocketIO::CONTENT_TYPE]
|
256
|
+
content_type(opts[:type] || File.extname(path), default: RocketIO::APPLICATION_OCTET_STREAM)
|
257
|
+
end
|
258
|
+
|
259
|
+
disposition = opts[:disposition]
|
260
|
+
filename = opts[:filename]
|
261
|
+
disposition = 'attachment' if disposition.nil? and filename
|
262
|
+
filename = path if filename.nil?
|
263
|
+
attachment(filename, disposition) if disposition
|
264
|
+
|
265
|
+
last_modified(opts[:last_modified]) if opts[:last_modified]
|
266
|
+
|
267
|
+
file = Rack::File.new(nil)
|
268
|
+
file.path = path
|
269
|
+
result = file.serving(env)
|
270
|
+
result[1].each { |k,v| headers[k] ||= v }
|
271
|
+
headers[RocketIO::CONTENT_LENGTH] = result[1][RocketIO::CONTENT_LENGTH]
|
272
|
+
opts[:status] &&= Integer(opts[:status])
|
273
|
+
response.status = opts[:status] || result[0]
|
274
|
+
response.body = result[2]
|
275
|
+
halt
|
276
|
+
rescue Errno::ENOENT
|
277
|
+
error(404)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
281
|
+
# Any number of non-value directives (:public, :private, :no_cache,
|
282
|
+
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
|
283
|
+
# a Hash of value directives (:max_age, :min_stale, :s_max_age).
|
284
|
+
#
|
285
|
+
# cache_control :public, :must_revalidate, :max_age => 60
|
286
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
287
|
+
#
|
288
|
+
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
289
|
+
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
290
|
+
def cache_control *values
|
291
|
+
if values.last.kind_of?(Hash)
|
292
|
+
hash = values.pop
|
293
|
+
hash.reject! { |k,v| v == false }
|
294
|
+
hash.reject! { |k,v| values << k if v == true }
|
295
|
+
else
|
296
|
+
hash = {}
|
297
|
+
end
|
298
|
+
|
299
|
+
values.map! { |value| value.to_s.tr('_','-') }
|
300
|
+
hash.each do |key, value|
|
301
|
+
key = key.to_s.tr('_', '-')
|
302
|
+
value = value.to_i if key == "max-age"
|
303
|
+
values << "#{key}=#{value}"
|
304
|
+
end
|
305
|
+
|
306
|
+
response[RocketIO::CACHE_CONTROL] = values.join(', ') if values.any?
|
307
|
+
end
|
308
|
+
|
309
|
+
# Set the Expires header and Cache-Control/max-age directive. Amount
|
310
|
+
# can be an integer number of seconds in the future or a Time object
|
311
|
+
# indicating when the response should be considered "stale". The remaining
|
312
|
+
# "values" arguments are passed to the #cache_control helper:
|
313
|
+
#
|
314
|
+
# expires 500, :public, :must_revalidate
|
315
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
316
|
+
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
317
|
+
#
|
318
|
+
def expires amount, *values
|
319
|
+
values << {} unless values.last.kind_of?(Hash)
|
320
|
+
|
321
|
+
if amount.is_a?(Integer)
|
322
|
+
time = Time.now + amount.to_i
|
323
|
+
max_age = amount
|
324
|
+
else
|
325
|
+
time = time_for amount
|
326
|
+
max_age = time - Time.now
|
327
|
+
end
|
328
|
+
|
329
|
+
values.last.merge!(:max_age => max_age)
|
330
|
+
cache_control(*values)
|
331
|
+
|
332
|
+
response[RocketIO::EXPIRES] = time.httpdate
|
333
|
+
end
|
334
|
+
|
335
|
+
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
|
336
|
+
# and halt if conditional GET matches. The +time+ argument is a Time,
|
337
|
+
# DateTime, or other object that responds to +to_time+.
|
338
|
+
#
|
339
|
+
# When the current request includes an 'If-Modified-Since' header that is
|
340
|
+
# equal or later than the time specified, execution is immediately halted
|
341
|
+
# with a '304 Not Modified' response.
|
342
|
+
def last_modified time
|
343
|
+
return unless time
|
344
|
+
time = time_for(time)
|
345
|
+
response[RocketIO::LAST_MODIFIED] = time.httpdate
|
346
|
+
return if env[RocketIO::HTTP_IF_NONE_MATCH]
|
347
|
+
|
348
|
+
if response.ok? && env[RocketIO::HTTP_IF_MODIFIED_SINCE]
|
349
|
+
# compare based on seconds since epoch
|
350
|
+
since = Time.httpdate(env[RocketIO::HTTP_IF_MODIFIED_SINCE]).to_i
|
351
|
+
halt(304) if since >= time.to_i
|
352
|
+
end
|
353
|
+
|
354
|
+
if (response.successful? || response.precondition_failed?) && env[RocketIO::HTTP_IF_UNMODIFIED_SINCE]
|
355
|
+
# compare based on seconds since epoch
|
356
|
+
since = Time.httpdate(env[RocketIO::HTTP_IF_UNMODIFIED_SINCE]).to_i
|
357
|
+
halt(412) if since < time.to_i
|
358
|
+
end
|
359
|
+
rescue ArgumentError
|
360
|
+
end
|
361
|
+
|
362
|
+
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
363
|
+
# GET matches. The +value+ argument is an identifier that uniquely
|
364
|
+
# identifies the current version of the resource. The +kind+ argument
|
365
|
+
# indicates whether the etag should be used as a :strong (default) or :weak
|
366
|
+
# cache validator.
|
367
|
+
#
|
368
|
+
# When the current request includes an 'If-None-Match' header with a
|
369
|
+
# matching etag, execution is immediately halted. If the request method is
|
370
|
+
# GET or HEAD, a '304 Not Modified' response is sent.
|
371
|
+
def etag value, options = {}
|
372
|
+
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
373
|
+
options = {:kind => options} unless Hash === options
|
374
|
+
kind = options[:kind] || :strong
|
375
|
+
new_resource = options.fetch(:new_resource) { request.post? }
|
376
|
+
|
377
|
+
unless RocketIO::ETAG_KINDS.include?(kind)
|
378
|
+
raise ArgumentError, ":strong or :weak expected"
|
379
|
+
end
|
380
|
+
|
381
|
+
value = '"%s"' % value
|
382
|
+
value = "W/#{value}" if kind == :weak
|
383
|
+
response[RocketIO::ETAG] = value
|
384
|
+
|
385
|
+
if response.successful? || response.not_modified?
|
386
|
+
if etag_matches?(env[RocketIO::HTTP_IF_NONE_MATCH], new_resource)
|
387
|
+
response.status = request.safe? ? 304 : 412
|
388
|
+
halt
|
389
|
+
end
|
390
|
+
|
391
|
+
if env[RocketIO::HTTP_IF_MATCH]
|
392
|
+
unless etag_matches?(env[RocketIO::HTTP_IF_MATCH], new_resource)
|
393
|
+
response.status = 412
|
394
|
+
halt
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# Sugar for redirect (example: redirect back)
|
401
|
+
def back
|
402
|
+
request.referer
|
403
|
+
end
|
404
|
+
|
405
|
+
# Generates a Time object from the given value.
|
406
|
+
# Used by #expires and #last_modified.
|
407
|
+
def time_for value
|
408
|
+
if value.respond_to?(:to_time)
|
409
|
+
value.to_time
|
410
|
+
elsif value.is_a?(Time)
|
411
|
+
value
|
412
|
+
elsif value.respond_to?(:new_offset)
|
413
|
+
# DateTime#to_time does the same on 1.9
|
414
|
+
d = value.new_offset 0
|
415
|
+
t = Time.utc(d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction)
|
416
|
+
t.getlocal
|
417
|
+
elsif value.respond_to?(:mday)
|
418
|
+
# Date#to_time does the same on 1.9
|
419
|
+
Time.local(value.year, value.mon, value.mday)
|
420
|
+
elsif value.is_a? Numeric
|
421
|
+
Time.at value
|
422
|
+
else
|
423
|
+
Time.parse value.to_s
|
424
|
+
end
|
425
|
+
rescue ArgumentError => boom
|
426
|
+
raise boom
|
427
|
+
rescue Exception
|
428
|
+
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
429
|
+
end
|
430
|
+
|
431
|
+
private
|
432
|
+
# Helper method checking if a ETag value list includes the current ETag.
|
433
|
+
def etag_matches? list, new_resource = request.post?
|
434
|
+
return !new_resource if list == '*'
|
435
|
+
list.to_s.split(/\s*,\s*/).include? response[RocketIO::ETAG]
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|