rocketio 0.0.0.pre.alpha → 0.0.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -2
  3. data/LICENSE.txt +22 -0
  4. data/README.md +5 -22
  5. data/Rakefile +1 -7
  6. data/lib/rocketio.rb +3 -131
  7. data/lib/rocketio/version.rb +2 -2
  8. data/rocketio.gemspec +17 -21
  9. metadata +11 -146
  10. data/.travis.yml +0 -3
  11. data/bin/console +0 -14
  12. data/bin/setup +0 -7
  13. data/lib/rocketio/application.rb +0 -31
  14. data/lib/rocketio/controller.rb +0 -288
  15. data/lib/rocketio/controller/authentication.rb +0 -141
  16. data/lib/rocketio/controller/authorization.rb +0 -53
  17. data/lib/rocketio/controller/cookies.rb +0 -59
  18. data/lib/rocketio/controller/error_handlers.rb +0 -89
  19. data/lib/rocketio/controller/filters.rb +0 -119
  20. data/lib/rocketio/controller/flash.rb +0 -21
  21. data/lib/rocketio/controller/helpers.rb +0 -438
  22. data/lib/rocketio/controller/middleware.rb +0 -32
  23. data/lib/rocketio/controller/render.rb +0 -148
  24. data/lib/rocketio/controller/render/engine.rb +0 -76
  25. data/lib/rocketio/controller/render/layout.rb +0 -27
  26. data/lib/rocketio/controller/render/layouts.rb +0 -85
  27. data/lib/rocketio/controller/render/templates.rb +0 -83
  28. data/lib/rocketio/controller/request.rb +0 -115
  29. data/lib/rocketio/controller/response.rb +0 -84
  30. data/lib/rocketio/controller/sessions.rb +0 -64
  31. data/lib/rocketio/controller/token_auth.rb +0 -118
  32. data/lib/rocketio/controller/websocket.rb +0 -21
  33. data/lib/rocketio/error_templates/404.html +0 -3
  34. data/lib/rocketio/error_templates/409.html +0 -7
  35. data/lib/rocketio/error_templates/500.html +0 -3
  36. data/lib/rocketio/error_templates/501.html +0 -6
  37. data/lib/rocketio/error_templates/layout.html +0 -1
  38. data/lib/rocketio/exceptions.rb +0 -4
  39. data/lib/rocketio/router.rb +0 -65
  40. data/lib/rocketio/util.rb +0 -122
  41. data/test/aliases_test.rb +0 -54
  42. data/test/authentication_test.rb +0 -307
  43. data/test/authorization_test.rb +0 -91
  44. data/test/cache_control_test.rb +0 -268
  45. data/test/content_type_test.rb +0 -124
  46. data/test/cookies_test.rb +0 -49
  47. data/test/error_handlers_test.rb +0 -125
  48. data/test/etag_test.rb +0 -445
  49. data/test/filters_test.rb +0 -177
  50. data/test/halt_test.rb +0 -73
  51. data/test/helpers_test.rb +0 -171
  52. data/test/middleware_test.rb +0 -57
  53. data/test/redirect_test.rb +0 -135
  54. data/test/render/engine_test.rb +0 -71
  55. data/test/render/get.erb +0 -1
  56. data/test/render/items.erb +0 -1
  57. data/test/render/layout.erb +0 -1
  58. data/test/render/layout_test.rb +0 -104
  59. data/test/render/layouts/master.erb +0 -1
  60. data/test/render/layouts_test.rb +0 -145
  61. data/test/render/master.erb +0 -1
  62. data/test/render/post.erb +0 -1
  63. data/test/render/put.erb +0 -1
  64. data/test/render/render_test.rb +0 -101
  65. data/test/render/setup.rb +0 -14
  66. data/test/render/templates/a/get.erb +0 -1
  67. data/test/render/templates/master.erb +0 -1
  68. data/test/render/templates_test.rb +0 -146
  69. data/test/request_test.rb +0 -105
  70. data/test/response_test.rb +0 -119
  71. data/test/routes_test.rb +0 -70
  72. data/test/sendfile_test.rb +0 -209
  73. data/test/sessions_test.rb +0 -176
  74. data/test/setup.rb +0 -59
@@ -1,53 +0,0 @@
1
- require 'rocketio/controller/token_auth'
2
-
3
- module RocketIO
4
- class Controller
5
-
6
- # easily restrict access to controller using token auth
7
- #
8
- # @example simple Token example
9
- #
10
- # class User < RocketIO::Controller
11
- # token_auth { |token| token == 'secret' }
12
- # end
13
- #
14
- def self.token_auth *args, &block
15
- opts = args.last.is_a?(Hash) ? args.pop : {}
16
- (args.any? ? args.map!(&:to_sym) : RocketIO::REQUEST_METHODS.values).each do |rm|
17
- (@__token_auth__ ||= {})[rm] = {
18
- realm: opts[:realm] || RocketIO::DEFAULT_TOKEN_AUTH_REALM.freeze,
19
- block: block
20
- }
21
- end
22
- define_token_auth_methods
23
- end
24
-
25
- def self.define_token_auth_methods source = self
26
- prompts = allocate.token_auth.merge(source.instance_variable_get(:@__token_auth__) || {}).freeze
27
- return if prompts.empty?
28
- define_method(:token_auth) {prompts}
29
- end
30
-
31
- def token_auth; RocketIO::EMPTY_HASH end
32
-
33
- def validate_or_request_authorization_if_needed
34
- return unless auth = token_auth[requested_method]
35
- return if validate_token_auth(&auth[:block])
36
- throw(:__response__, request_token_auth(auth[:realm]))
37
- end
38
-
39
- def validate_or_request_token_auth realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM, &block
40
- validate_token_auth(&block) || request_token_auth(realm)
41
- end
42
-
43
- def validate_token_auth &block
44
- RocketIO::TokenAuth.authenticate(env, &block)
45
- end
46
- alias valid_token_auth? validate_token_auth
47
-
48
- def request_token_auth realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM
49
- RocketIO::TokenAuth.authentication_request(realm)
50
- end
51
- alias request_token_auth! request_token_auth
52
- end
53
- end
@@ -1,59 +0,0 @@
1
- module RocketIO
2
- class Controller
3
-
4
- # shorthand for `request.cookies`, `response.set_cookie` and `response.delete_cookie`
5
- #
6
- # @example Setting a cookie
7
- # cookies['cookie-name'] = 'value'
8
- #
9
- # @example Reading a cookie
10
- # cookies['cookie-name']
11
- #
12
- # @example Setting a cookie with custom options
13
- # cookies['question_of_the_day'] = {
14
- # value: 'who is not who?',
15
- # expires: Date.today + 1,
16
- # secure: true
17
- # }
18
- #
19
- # @example Deleting a cookie
20
- # cookies.delete('cookie-name')
21
- #
22
- def cookies
23
- @__cookies__ ||= RocketIO::Cookies.new(request.cookies, response)
24
- end
25
- end
26
- end
27
-
28
- module RocketIO
29
- class Cookies
30
-
31
- def initialize cookies, response
32
- @cookies = RocketIO.indifferent_params(cookies)
33
- @response = response
34
- end
35
-
36
- # set cookie header
37
- #
38
- # @param [String, Symbol] key
39
- # @param [String, Hash] val
40
- #
41
- def []= key, val
42
- @response.set_cookie(key, val)
43
- end
44
-
45
- # get cookie by key
46
- def [] key
47
- @cookies[key]
48
- end
49
-
50
- # instruct browser to delete a cookie
51
- #
52
- # @param [String, Symbol] key
53
- # @param [Hash] opts
54
- #
55
- def delete key, opts ={}
56
- @response.delete_cookie(key, opts)
57
- end
58
- end
59
- end
@@ -1,89 +0,0 @@
1
- module RocketIO
2
- class Controller
3
-
4
- # define error handlers
5
- #
6
- # @example define a handler that will process 404 errors
7
- # class Pages < RocketIO::Controller
8
- #
9
- # error 404 do |id|
10
- # "Sorry, looks like item with ID #{id.to_i} does not exists"
11
- # end
12
- #
13
- # def get id
14
- # item = Item.find_by(id: id) || error(404, id)
15
- # end
16
- # end
17
- #
18
- # @example define a handler that will process fatal errors
19
- # class Pages < RocketIO::Controller
20
- #
21
- # error 500 do |exception|
22
- # "Fatal error occurred: " + html_escape(exception.message)
23
- # end
24
- #
25
- # def get
26
- # # any exception raised here will be handled by the handler above
27
- # end
28
- # end
29
- #
30
- def self.error code, &block
31
- code = code.to_i
32
- code > 0 || raise(ArgumentError, 'Error code should be a number')
33
- block || raise(ArgumentError, 'block missing')
34
- (@__error_handlers__ ||= {})[code] = block
35
- define_error_handlers_methods
36
- end
37
-
38
- def self.define_error_handlers_methods source = self
39
- handlers = (source.instance_variable_get(:@__error_handlers__) || {}).each_with_object({}) do |(code,proc),o|
40
- o[code] = :"__#{code}_error_handler__"
41
- define_method(o[code], &proc)
42
- end
43
- handlers.update(allocate.error_handlers)
44
- return if handlers.empty?
45
- handlers.freeze
46
- define_method(:error_handlers) {handlers}
47
- end
48
-
49
- def error_handlers; RocketIO::EMPTY_HASH end
50
-
51
- # if there is a handler defined for given code it will be executed and the result used as body.
52
- # otherwise the `error` behaves exactly as `halt`.
53
- #
54
- # given args will be passed either to handler(if any defined) or to `halt`
55
- #
56
- def error code, *args
57
- error_handlers[code] || halt(code, *args)
58
- halt(code, __send__(error_handlers[code], *args))
59
- end
60
- alias error! error
61
-
62
- # 404: Not Found
63
- error 404 do
64
- RocketIO.error_renderer(404, xhr?, env: env)
65
- end
66
-
67
- # 409: Wrong number of arguments received
68
- error 409 do
69
- RocketIO.error_renderer(409, xhr?, {
70
- env: env,
71
- controller: self.class,
72
- resolved_path: url,
73
- expected_parameters: parameters_policy[env[RocketIO::REQUEST_METHOD]],
74
- received_parameters: path_params
75
- })
76
- end
77
-
78
- # 500: Fatal Error
79
- error 500 do |error|
80
- error = StandardError.new(error) unless Exception === error
81
- RocketIO.error_renderer(500, xhr?, env: env, error: error)
82
- end
83
-
84
- # 501: Not Implemented
85
- error 501 do
86
- RocketIO.error_renderer(501, xhr?, env: env)
87
- end
88
- end
89
- end
@@ -1,119 +0,0 @@
1
- module RocketIO
2
- class Controller
3
-
4
- # define blocks to run before, around, after called method.
5
- # if no methods given, given block will run on any called method.
6
- #
7
- # @note call it without a block to define a void filter.
8
- # useful to override inherited filters.
9
- #
10
- # @note sub-controllers will inherit all filters from parent controller
11
- # and can override them selectively, by name
12
- #
13
- # @note wildcard filters will run before/around/after any methods,
14
- # that's it, if defining `before {}` and `before(:get) {}` filters
15
- # `get` method will run `before` filter then `before(:get)`
16
- #
17
- # @example run before any requested method, being it REST or websocket
18
- # before do
19
- # # some logic here
20
- # end
21
- #
22
- # @example run only before GET
23
- # before :get do
24
- # # some logic here
25
- # end
26
- #
27
- # @example run only around PUT and POST
28
- # around :put, :post do |app|
29
- # # some logic here
30
- # app.call
31
- # end
32
- #
33
- # @example run only after :register websocket call
34
- # after :register do
35
- # # some logic here
36
- # end
37
- #
38
- # @example define a filter that does nothing. useful to override inherited filters.
39
- # before :get
40
- #
41
- # @example run 2 blocks before GET
42
- # before do # wildcard filter, will run before any method
43
- # @user = User.find...
44
- # end
45
- #
46
- # before :get do # named filter, will run only before `get`
47
- # # wildcard filter already executed so we have `@user` variable here
48
- # @photo = @user.photos.find...
49
- # end
50
- #
51
- # # before calling `get` two filters will be executed:
52
- # # - wildcard one
53
- # # - named one
54
- # def get
55
- # # both @user and @photo variables available here
56
- # end
57
- #
58
- {
59
- before: proc {},
60
- around: proc {|app| app.call},
61
- after: proc {}
62
- }.each_pair do |filter,default_block|
63
- define_methods = :"define_#{filter}_methods"
64
- var = :"@__#{filter}_filters__"
65
-
66
- define_singleton_method filter do |*methods,&block|
67
- instance_variable_get(var) || instance_variable_set(var, {})
68
- methods = [:*] if methods.empty?
69
- methods.each do |meth|
70
- instance_variable_get(var)[meth.to_sym] = block || default_block
71
- end
72
- __send__(define_methods)
73
- end
74
-
75
- define_singleton_method define_methods do |source = self|
76
- filters = (source.instance_variable_get(var) || {}).each_with_object({}) do |(meth,proc),o|
77
- o[meth] = :"__#{filter}_#{meth}__"
78
- define_method(o[meth], &proc)
79
- end
80
- filters.update(allocate.__send__(filter))
81
- return unless filters.any?
82
- filters.freeze
83
- define_method(filter) {filters}
84
- end
85
- end
86
-
87
- def before; RocketIO::EMPTY_HASH end
88
- def around; RocketIO::EMPTY_HASH end
89
- def after; RocketIO::EMPTY_HASH end
90
-
91
- def invoke_before_filter method = requested_method
92
- __send__(before[:*]) if before[:*]
93
- __send__(before[method]) if before[method]
94
- end
95
-
96
- # passing blocks somehow tends to add some overhead
97
- # so passing the proc as a common argument
98
- def invoke_around_filter method = requested_method, block
99
- if around[:*]
100
- __send__ around[:*], proc {
101
- if around[method]
102
- __send__(around[method], block)
103
- else
104
- block.call
105
- end
106
- }
107
- elsif around[method]
108
- __send__(around[method], block)
109
- else
110
- block.call
111
- end
112
- end
113
-
114
- def invoke_after_filter method = requested_method
115
- __send__(after[:*]) if after[:*]
116
- __send__(after[method]) if after[method]
117
- end
118
- end
119
- end
@@ -1,21 +0,0 @@
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
@@ -1,438 +0,0 @@
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