rocketio 0.0.0.pre.alpha → 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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