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.
- checksums.yaml +4 -4
- data/.gitignore +5 -2
- data/LICENSE.txt +22 -0
- data/README.md +5 -22
- data/Rakefile +1 -7
- data/lib/rocketio.rb +3 -131
- data/lib/rocketio/version.rb +2 -2
- data/rocketio.gemspec +17 -21
- metadata +11 -146
- data/.travis.yml +0 -3
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/lib/rocketio/application.rb +0 -31
- data/lib/rocketio/controller.rb +0 -288
- data/lib/rocketio/controller/authentication.rb +0 -141
- data/lib/rocketio/controller/authorization.rb +0 -53
- data/lib/rocketio/controller/cookies.rb +0 -59
- data/lib/rocketio/controller/error_handlers.rb +0 -89
- data/lib/rocketio/controller/filters.rb +0 -119
- data/lib/rocketio/controller/flash.rb +0 -21
- data/lib/rocketio/controller/helpers.rb +0 -438
- data/lib/rocketio/controller/middleware.rb +0 -32
- data/lib/rocketio/controller/render.rb +0 -148
- data/lib/rocketio/controller/render/engine.rb +0 -76
- data/lib/rocketio/controller/render/layout.rb +0 -27
- data/lib/rocketio/controller/render/layouts.rb +0 -85
- data/lib/rocketio/controller/render/templates.rb +0 -83
- data/lib/rocketio/controller/request.rb +0 -115
- data/lib/rocketio/controller/response.rb +0 -84
- data/lib/rocketio/controller/sessions.rb +0 -64
- data/lib/rocketio/controller/token_auth.rb +0 -118
- data/lib/rocketio/controller/websocket.rb +0 -21
- data/lib/rocketio/error_templates/404.html +0 -3
- data/lib/rocketio/error_templates/409.html +0 -7
- data/lib/rocketio/error_templates/500.html +0 -3
- data/lib/rocketio/error_templates/501.html +0 -6
- data/lib/rocketio/error_templates/layout.html +0 -1
- data/lib/rocketio/exceptions.rb +0 -4
- data/lib/rocketio/router.rb +0 -65
- data/lib/rocketio/util.rb +0 -122
- data/test/aliases_test.rb +0 -54
- data/test/authentication_test.rb +0 -307
- data/test/authorization_test.rb +0 -91
- data/test/cache_control_test.rb +0 -268
- data/test/content_type_test.rb +0 -124
- data/test/cookies_test.rb +0 -49
- data/test/error_handlers_test.rb +0 -125
- data/test/etag_test.rb +0 -445
- data/test/filters_test.rb +0 -177
- data/test/halt_test.rb +0 -73
- data/test/helpers_test.rb +0 -171
- data/test/middleware_test.rb +0 -57
- data/test/redirect_test.rb +0 -135
- data/test/render/engine_test.rb +0 -71
- data/test/render/get.erb +0 -1
- data/test/render/items.erb +0 -1
- data/test/render/layout.erb +0 -1
- data/test/render/layout_test.rb +0 -104
- data/test/render/layouts/master.erb +0 -1
- data/test/render/layouts_test.rb +0 -145
- data/test/render/master.erb +0 -1
- data/test/render/post.erb +0 -1
- data/test/render/put.erb +0 -1
- data/test/render/render_test.rb +0 -101
- data/test/render/setup.rb +0 -14
- data/test/render/templates/a/get.erb +0 -1
- data/test/render/templates/master.erb +0 -1
- data/test/render/templates_test.rb +0 -146
- data/test/request_test.rb +0 -105
- data/test/response_test.rb +0 -119
- data/test/routes_test.rb +0 -70
- data/test/sendfile_test.rb +0 -209
- data/test/sessions_test.rb +0 -176
- 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
|