Class: RocketIO::Controller

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/rocketio/controller.rb,
lib/rocketio/controller/cookies.rb,
lib/rocketio/controller/filters.rb,
lib/rocketio/controller/helpers.rb,
lib/rocketio/controller/sessions.rb,
lib/rocketio/controller/websocket.rb,
lib/rocketio/controller/middleware.rb,
lib/rocketio/controller/authorization.rb,
lib/rocketio/controller/authentication.rb,
lib/rocketio/controller/error_handlers.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(requested_method = RocketIO::INDEX_METHOD, path_params = nil, params = nil) ⇒ Controller

Returns a new instance of Controller.



17
18
19
20
21
22
23
24
25
26
# File 'lib/rocketio/controller.rb', line 17

def initialize requested_method = RocketIO::INDEX_METHOD, path_params = nil, params = nil
  @__requested_method__ = requested_method.to_sym

  @__path_params_array__ = path_params || RocketIO::EMPTY_ARRAY
  @__path_params_array__.freeze

  if params
    @__params__ = indifferent_params(params)
  end
end

Class Method Details

.alias_url(path) ⇒ Object

allow same controller to serve multiple URLs

Examples:

Users controller will serve both /users and /members URLs


class Users < RocketIO::Controller
  alias_url :members

end

Parameters:

  • path (String || Symbol)


216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/rocketio/controller.rb', line 216

def alias_url path
  path = path.to_s
  path = if path =~ /\A\//
    path
  else
    if superclass == Object
      RocketIO.rootify_path(path)
    else
      RocketIO.rootify_path(superclass.url, path)
    end
  end.freeze
  aliases.push(path)
end

.aliasesObject



230
231
232
# File 'lib/rocketio/controller.rb', line 230

def aliases
  @__aliases__ ||= []
end

.apiObject



130
131
132
# File 'lib/rocketio/controller.rb', line 130

def api
  @__api__
end

.basic_auth(*args, &block) ⇒ Object

Note:

authorization is composable, that’s it, if superclass is protecting :a and current controller is protecting :b method, both :a and :b will be protected in current controller

easily restrict access to controller using basic auth

Examples:

protect all methods on any request methods

basic_auth do |user,pass|
  user == 'admin' && pass == 'super secret password'
end

protect all methods only on POST, PUT and DELETE request methods

basic_auth do |user,pass|
  post? || put? || delete? ?
    user == 'admin' && pass == 'super secret password' :
    true
end

protect only :edit

basic_auth :edit do |user,pass|
  user == 'reader' && pass == 'readPass'
end

Parameters:

  • block (Proc)


30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/rocketio/controller/authentication.rb', line 30

def self.basic_auth *args, &block
  opts = args.last.is_a?(Hash) ? args.pop : {}
  (args.any? ? args.map(&:to_sym) : [:*]).each do |method|
    (@__basic_auth__ ||= {})[method] = {
      class:     Rack::Auth::Basic,
      arguments: [opts[:realm] || RocketIO::DEFAULT_AUTH_REALM].freeze,
      block:     block,
      mock:      RocketIO::HTTP_AUTHORIZATION_MOCKS[:basic]
    }.freeze
  end
  define_basic_auth_methods
end

.call(env) ⇒ Object



265
266
267
268
269
270
271
272
273
# File 'lib/rocketio/controller.rb', line 265

def call env
  path_params = env[RocketIO::PATH_INFO].sub(url, RocketIO::EMPTY_STRING).scan(RocketIO::PATH_SPLITTER)
  method = if path_params.any? && api[path_params[0].to_sym]
    path_params.slice!(0).to_sym
  else
    RocketIO::INDEX_METHOD
  end
  new(method, path_params).call(env)
end

.define_basic_auth_methods(source = self) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/rocketio/controller/authentication.rb', line 43

def self.define_basic_auth_methods source = self
  prompts = (source.instance_variable_get(:@__basic_auth__) || {}).each_with_object(allocate.basic_auth.dup) do |(m,p),o|
    method = :"__basic_auth__#{m}__"
    api.delete define_method(method, &p[:block])
    o[m] = p.merge(method: method).freeze
  end.freeze
  return if prompts.empty?
  api.delete define_method(:basic_auth) {prompts}
end

.define_digest_auth_methods(source = self) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/rocketio/controller/authentication.rb', line 96

def self.define_digest_auth_methods source = self
  prompts = (source.instance_variable_get(:@__digest_auth__) || {}).each_with_object(allocate.digest_auth.dup) do |(m,p),o|
    method = :"__digest_auth__#{m}__"
    api.delete define_method(method, &p[:block])
    o[m] = p.merge(method: method).freeze
  end.freeze
  return if prompts.empty?
  api.delete define_method(:digest_auth) {prompts}
end

.define_error_handlers_methods(source = self) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/rocketio/controller/error_handlers.rb', line 38

def self.define_error_handlers_methods source = self
  handlers = (source.instance_variable_get(:@__error_handlers__) || {}).each_with_object({}) do |(code,block),o|
    o[code] = :"__#{code}_error_handler__"
    api.delete define_method(o[code], &block)
  end
  handlers.update(allocate.error_handlers)
  return if handlers.empty?
  handlers.freeze
  api.delete define_method(:error_handlers) {handlers}
end

.define_middleware_methods(source = self) ⇒ Object



24
25
26
27
28
# File 'lib/rocketio/controller/middleware.rb', line 24

def self.define_middleware_methods source = self
  middleware = ((source.instance_variable_get(:@__middleware__) || []) + allocate.middleware).uniq.freeze
  return if middleware.empty?
  api.delete define_method(:middleware) {middleware}
end

.define_sessions_methods(source = self) ⇒ Object



56
57
58
59
60
# File 'lib/rocketio/controller/sessions.rb', line 56

def self.define_sessions_methods source = self
  return unless source.instance_variables.include?(:@__sessions__)
  sessions = source.instance_variable_get(:@__sessions__)
  api.delete define_method(:sessions) {sessions}
end

.define_token_auth_methods(source = self) ⇒ Object



25
26
27
28
29
# File 'lib/rocketio/controller/authorization.rb', line 25

def self.define_token_auth_methods source = self
  prompts = allocate.token_auth.merge(source.instance_variable_get(:@__token_auth__) || {}).freeze
  return if prompts.empty?
  api.delete define_method(:token_auth) {prompts}
end

.digest_auth(*args, &block) ⇒ Object

easily restrict access to controller using digest auth

Examples:

protect all methods using hashed passwords

# hash the password somewhere in irb:
# ::Digest::MD5.hexdigest 'admin:AccessRestricted:somePassword'
#                   username ^      realm ^       password ^

#=> 9d77d54decc22cdcfb670b7b79ee0ef0

digest_auth :passwords_hashed => true, :realm => 'AccessRestricted' do |user|
  {'admin' => '9d77d54decc22cdcfb670b7b79ee0ef0'}[user]
end

protect all methods using plain passwords

digest_auth do |user|
  {'admin' => 'password'}[user]
end

protect only :create and :edit methods

digest_auth :create, :edit do |user|
  {'admin' => 'password'}[user]
end

Parameters:

  • block (Proc)


81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rocketio/controller/authentication.rb', line 81

def self.digest_auth *args, &block
  opts = args.last.is_a?(Hash) ? args.pop : {}
  opts[:realm]  ||= RocketIO::DEFAULT_AUTH_REALM
  opts[:opaque] ||= opts[:realm]
  (args.any? ? args.map(&:to_sym) : [:*]).each do |method|
    (@__digest_auth__ ||= {})[method] = {
      class:     Rack::Auth::Digest::MD5,
      arguments: [opts].freeze,
      block:     block,
      mock:      RocketIO::HTTP_AUTHORIZATION_MOCKS[:digest]
    }.freeze
  end
  define_digest_auth_methods
end

.dirname(*args) ⇒ Object



261
262
263
# File 'lib/rocketio/controller.rb', line 261

def dirname *args
  ::File.join(@__dirname__, args.map!(&:to_s))
end

.environmentObject



275
# File 'lib/rocketio/controller.rb', line 275

def environment; RocketIO.environment end

.error(code, &block) ⇒ Object

define error handlers

Examples:

define a handler that will process 404 errors

class Pages < RocketIO::Controller

  error 404 do |id|
    "Sorry, looks like item with ID #{id.to_i} does not exists"
  end

  def get id
    item = Item.find_by(id: id) || error(404, id)
  end
end

define a handler that will process fatal errors

class Pages < RocketIO::Controller

  error 500 do |exception|
    "Fatal error occurred: " + html_escape(exception.message)
  end

  def get
    # any exception raised here will be handled by the handler above
  end
end


30
31
32
33
34
35
36
# File 'lib/rocketio/controller/error_handlers.rb', line 30

def self.error code, &block
  code = code.to_i
  code > 0 || raise(ArgumentError, 'Error code should be a number')
  block || raise(ArgumentError, 'block missing')
  (@__error_handlers__ ||= {})[code] = block
  define_error_handlers_methods
end

.import(setup, from:) ⇒ Object

import some config from some controller



135
136
137
# File 'lib/rocketio/controller.rb', line 135

def import setup, from:
  __send__(:"define_#{setup}_methods", from)
end

.inherited(base) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rocketio/controller.rb', line 139

def inherited base
  # registering a new controller
  RocketIO.controllers.push(base)

  base.instance_variable_set(:@__api__, self.api.dup)

  # new controller inherits all setups from superclass
  RocketIO::INHERITABLE_SETUPS.each {|s| base.import(s, from: self)}

  # removing superclass name from new controller name
  path = RocketIO.underscore(base.name.to_s.sub(self.name.to_s + '::', '').gsub('::', '/'))

  # new controller uses for URL its underscored name prefixed by superclass URL
  base.map RocketIO.rootify_path(url, path)

  # setting dirname for new controller
  base.instance_variable_set(:@__dirname__, RocketIO.caller_to_dirname(caller).freeze)
end

.map(path) ⇒ Object

Note:
Note:

if given URL starts with a slash it will ignore class name and set URL as is

by default controllers will use underscored name for base URL. this method allow to set a custom base URL.

Examples:

Users::Register will listen on /users/register by default

class Users
  class Register < RocketIO

  end
end

make Users::Register to listen on /users/join instead of /users/register


class Users
  class Register < RocketIO::Controller
    map :join

  end
end

make Users::Register to listen on /members/join instead of /users/register


class Users < RocketIO::Controller
  class Register < self
    map '/members/join'

  end
end

Parameters:

  • path (String || Symbol)


192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rocketio/controller.rb', line 192

def map path
  path = path.to_s
  @__url__ = if path =~ /\A\//
    path
  else
    if superclass == Object
      RocketIO.rootify_path(path)
    else
      RocketIO.rootify_path(superclass.url, path)
    end
  end.freeze
end

.method_added(meth) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
# File 'lib/rocketio/controller.rb', line 249

def method_added meth
  return if self == RocketIO::Controller
  return unless public_instance_methods(false).include?(meth)

  params, path_params = instance_method(meth).parameters.partition {|p| p[0] =~ /\Akey/}

  api[meth] = {
    path_params: RocketIO.path_params(path_params).freeze,
    params_accepted_as_last_argument?: params.any?,
  }
end

.sessions(pool = (noargs = true; nil), opts = {}) ⇒ Object

Note:

to disable sessions for some controller set sessions to nil or false

setup sessions. there are 2 setups out of the box:

- cookies
- memcache

you can also use your own setup

Examples:

keep sessions in cookies

sessions :cookies

keep sessions in cookies using custom options

sessions :cookies, secret: 'someSecret', domain: 'some.domain'

keep sessions in memcache

sessions :memcache

use a custom setup, i.e. github.com/migrs/rack-session-mongo

$ gem install rack-session-mongo

require 'rack/session/mongo'

class Pages < RocketIO::Controller
  sessions Rack::Session::Mongo
end

disable sessions for current controller

sessions nil # or false

Parameters:

  • pool (Symbol, Class) (defaults to: (noargs = true; nil))
  • opts (Hash) (defaults to: {})


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/rocketio/controller/sessions.rb', line 36

def self.sessions pool = (noargs = true; nil), opts = {}
  @__sessions__ = if pool.nil? || pool == false
    nil
  else
    if pool == :cookies && opts[:secret].nil?
      opts[:secret] = ::Digest::MD5.hexdigest([Time.now.to_f, rand]*'')
    end
    pool = case pool
    when :cookies
      ::Rack::Session::Cookie
    when :memcache
      ::Rack::Session::Memcache
    else
      pool
    end
    [pool, opts]
  end
  define_sessions_methods
end

.token_auth(*args, &block) ⇒ Object

easily restrict access to controller using token auth

Examples:

simple Token example


class User < RocketIO::Controller
  token_auth { |token| token == 'secret' }
end


14
15
16
17
18
19
20
21
22
23
# File 'lib/rocketio/controller/authorization.rb', line 14

def self.token_auth *args, &block
  opts = args.last.is_a?(Hash) ? args.pop : {}
  (args.any? ? args.map(&:to_sym) : [:*]).each do |method|
    (@__token_auth__ ||= {})[method] = {
      realm: opts[:realm] || RocketIO::DEFAULT_TOKEN_AUTH_REALM.freeze,
      block: block
    }
  end
  define_token_auth_methods
end

.url(*args) ⇒ String

build a URL from given chunks prefixing them with controller’s baseurl

Parameters:

  • *args (Array)

Returns:

  • (String)


239
240
241
242
243
244
245
246
247
# File 'lib/rocketio/controller.rb', line 239

def url *args
  return @__url__ if args.empty?
  query = if args.last.is_a?(Hash)
    RocketIO::QUERY_PREFIX + ::Rack::Utils.build_nested_query(args.pop)
  else
    RocketIO::EMPTY_STRING
  end
  ::File.join(@__url__, args.map!(&:to_s)) + query
end

.use(w = nil, *a, &b) ⇒ Object

Note:

middleware is inheritable

storing Rack middleware to be called when a request handled by controller

Examples:

add a middleware to be used in current controller

and any controllers that inherits from it

class Users < RocketIO::Controller
  use Rack::CommonLogger
end

Parameters:

  • w (Class, Proc) (defaults to: nil)

    a valid Rack middleware that responds to ‘call`

  • *a (Array)

    any args to be passed at middleware initialization

  • &b (Proc)

    a block to be yielded at middleware initialization



19
20
21
22
# File 'lib/rocketio/controller/middleware.rb', line 19

def self.use w = nil, *a, &b
  (@__middleware__ ||= []).push(proc {|app| w.new(app, *a, &b)})
  define_middleware_methods
end

Instance Method Details

#__error__(code, *args) ⇒ Object

call defined error handler with given arguments.

Raises:

  • NotImplementedError if no handler defined for given code



66
67
68
69
# File 'lib/rocketio/controller/error_handlers.rb', line 66

def __error__ code, *args
  error_handlers[code] || raise(NotImplementedError, 'No handler defined for %s error' % code.inspect)
  __send__(error_handlers[code], *args)
end

#__parse_json_body__Object



82
83
84
# File 'lib/rocketio/controller/helpers.rb', line 82

def __parse_json_body__
  JSON.parse(env[RACK_INPUT].read)
end

#afterObject



113
# File 'lib/rocketio/controller/filters.rb', line 113

def after;  RocketIO::EMPTY_HASH end

#aroundObject



112
# File 'lib/rocketio/controller/filters.rb', line 112

def around; RocketIO::EMPTY_HASH end

#attachment(filename = nil, disposition = 'attachment') ⇒ Object

Set the Content-Disposition to “attachment” with the specified filename, instructing the user agents to prompt to save.



254
255
256
257
258
259
260
261
# File 'lib/rocketio/controller/helpers.rb', line 254

def attachment(filename = nil, disposition = 'attachment')
  response[RocketIO::CONTENT_DISPOSITION] = disposition.to_s
  if filename
    response[RocketIO::CONTENT_DISPOSITION] << ('; filename="%s"' % File.basename(filename))
    ext = File.extname(filename)
    content_type(ext) unless response[RocketIO::CONTENT_TYPE] or ext.empty?
  end
end

#authentication_required?Boolean

Returns:

  • (Boolean)


126
127
128
129
130
131
# File 'lib/rocketio/controller/authentication.rb', line 126

def authentication_required?
  digest_auth[requested_method] ||
    basic_auth[requested_method] ||
    digest_auth[:*] ||
    basic_auth[:*]
end

#authorization_required?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/rocketio/controller/authorization.rb', line 51

def authorization_required?
  token_auth[requested_method] || token_auth[:*]
end

#backObject

Sugar for redirect (example: redirect back)



410
411
412
# File 'lib/rocketio/controller/helpers.rb', line 410

def back
  request.referer
end

#basic_authObject



53
# File 'lib/rocketio/controller/authentication.rb', line 53

def basic_auth; RocketIO::EMPTY_HASH end

#beforeObject



111
# File 'lib/rocketio/controller/filters.rb', line 111

def before; RocketIO::EMPTY_HASH end

#cache_control(*values) ⇒ Object

Specify response freshness policy for HTTP caches (Cache-Control header). Any number of non-value directives (:public, :private, :no_cache, :no_store, :must_revalidate, :proxy_revalidate) may be passed along with a Hash of value directives (:max_age, :min_stale, :s_max_age).

cache_control :public, :must_revalidate, :max_age => 60
=> Cache-Control: public, must-revalidate, max-age=60

See RFC 2616 / 14.9 for more on standard cache control directives: tools.ietf.org/html/rfc2616#section-14.9.1



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/rocketio/controller/helpers.rb', line 299

def cache_control *values
  if values.last.kind_of?(Hash)
    hash = values.pop
    hash.reject! { |k,v| v == false }
    hash.reject! { |k,v| values << k if v == true }
  else
    hash = {}
  end

  values.map! { |value| value.to_s.tr('_','-') }
  hash.each do |key, value|
    key = key.to_s.tr('_', '-')
    value = value.to_i if key == "max-age"
    values << "#{key}=#{value}"
  end

  response[RocketIO::CACHE_CONTROL] = values.join(', ') if values.any?
end

#call(env) ⇒ Rack::Response

call requested method

Parameters:

  • env (Hash)

Returns:

  • (Rack::Response)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rocketio/controller.rb', line 33

def call env
  @__env__ = env
  catch :__response__ do

    api[requested_method] || error(501)

    if error_handlers[500]
      begin
        __call__
      rescue Exception => e
        error(500, e)
      end
    else
      __call__
    end
  end
end

#charset(charset) ⇒ Object

shorthand for content_type(charset: ‘something’)



205
206
207
# File 'lib/rocketio/controller/helpers.rb', line 205

def charset charset
  content_type(charset: charset)
end

#client_error?Boolean

whether or not the status is set to 4xx

Returns:

  • (Boolean)


53
54
55
# File 'lib/rocketio/controller/helpers.rb', line 53

def client_error?
  response.status.between? 400, 499
end

#content_type(*args) ⇒ Object

returns, set or update content type. if called without args it will return current content type. if called with a single argument, given argument will be set as content type. if a type and hash given it will set brand new content type composed of given type and opts. if only a hash given it will update current content type with given opts.

if no content type is set it will use default one + given opts.

Examples:

set content type

content_type '.json'

set content type with some params

content_type '.json', level: 1

add params to current content type

content_type comment: 'Boo!'


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/rocketio/controller/helpers.rb', line 225

def content_type *args
  return response[RocketIO::CONTENT_TYPE] if args.empty?
  params = args.last.is_a?(Hash) ? args.pop.map {|kv| kv.map!(&:to_s)}.to_h : {}
  default = params.delete('default')

  if type = args.first
    mime_type = mime_type(type) || default || raise(ArgumentError, "Unknown media type: %p" % type)
  else
    mime_type = response[RocketIO::CONTENT_TYPE]
  end
  mime_type ||= RocketIO::DEFAULT_CONTENT_TYPE

  mime_type, _params = mime_type.split(';')
  if _params
    params = _params.split(',').map! {|o| o.strip.split('=')}.to_h.merge!(params)
  end

  if params.any?
    mime_type << '; '
    mime_type << params.map do |key, val|
      val = val.inspect if val =~ /[";,]/
      [key, val]*'='
    end.join(', ')
  end
  response[RocketIO::CONTENT_TYPE] = mime_type
end

#cookiesObject

shorthand for ‘request.cookies`, `response.set_cookie` and `response.delete_cookie`

Examples:

Setting a cookie

cookies['cookie-name'] = 'value'

Reading a cookie

cookies['cookie-name']

Setting a cookie with custom options

cookies['question_of_the_day'] = {
  value: 'who is not who?',
  expires: Date.today + 1,
  secure: true
}

Deleting a cookie

cookies.delete('cookie-name')


22
23
24
# File 'lib/rocketio/controller/cookies.rb', line 22

def cookies
  @__cookies__ ||= RocketIO::Cookies.new(request.cookies, response)
end

#digest_authObject



106
# File 'lib/rocketio/controller/authentication.rb', line 106

def digest_auth; RocketIO::EMPTY_HASH end

#envObject



51
52
53
# File 'lib/rocketio/controller.rb', line 51

def env
  @__env__
end

#error(code, *args) ⇒ Object Also known as: error!

if there is a handler defined for given code it will be executed and the result used as body. otherwise the ‘error` behaves exactly as `halt`.

given args will be passed either to handler(if any defined) or to ‘halt`



56
57
58
59
# File 'lib/rocketio/controller/error_handlers.rb', line 56

def error code, *args
  error_handlers[code] || halt(code, *args)
  halt(code, __error__(code, *args))
end

#error_handlersObject



49
# File 'lib/rocketio/controller/error_handlers.rb', line 49

def error_handlers; RocketIO::EMPTY_HASH end

#etag(value, options = {}) ⇒ Object

Set the response entity tag (HTTP ‘ETag’ header) and halt if conditional GET matches. The value argument is an identifier that uniquely identifies the current version of the resource. The kind argument indicates whether the etag should be used as a :strong (default) or :weak cache validator.

When the current request includes an ‘If-None-Match’ header with a matching etag, execution is immediately halted. If the request method is GET or HEAD, a ‘304 Not Modified’ response is sent.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/rocketio/controller/helpers.rb', line 380

def etag value, options = {}
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
  options      = {:kind => options} unless Hash === options
  kind         = options[:kind] || :strong
  new_resource = options.fetch(:new_resource) { request.post? }

  unless RocketIO::ETAG_KINDS.include?(kind)
    raise ArgumentError, ":strong or :weak expected"
  end

  value = '"%s"' % value
  value = "W/#{value}" if kind == :weak
  response[RocketIO::ETAG] = value

  if response.successful? || response.not_modified?
    if etag_matches?(env[RocketIO::HTTP_IF_NONE_MATCH], new_resource)
      response.status = request.safe? ? 304 : 412
      halt
    end

    if env[RocketIO::HTTP_IF_MATCH]
      unless etag_matches?(env[RocketIO::HTTP_IF_MATCH], new_resource)
        response.status = 412
        halt
      end
    end
  end
end

#expires(amount, *values) ⇒ Object

Set the Expires header and Cache-Control/max-age directive. Amount can be an integer number of seconds in the future or a Time object indicating when the response should be considered “stale”. The remaining “values” arguments are passed to the #cache_control helper:

expires 500, :public, :must_revalidate
=> Cache-Control: public, must-revalidate, max-age=60
=> Expires: Mon, 08 Jun 2009 08:50:17 GMT


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/rocketio/controller/helpers.rb', line 327

def expires amount, *values
  values << {} unless values.last.kind_of?(Hash)

  if amount.is_a?(Integer)
    time    = Time.now + amount.to_i
    max_age = amount
  else
    time    = time_for amount
    max_age = time - Time.now
  end

  values.last.merge!(:max_age => max_age)
  cache_control(*values)

  response[RocketIO::EXPIRES] = time.httpdate
end

#flashObject

Examples:

flash[:alert] = 'some secret'
p flash[:alert] #=> "some secret"
p flash[:alert] #=> nil


160
161
162
# File 'lib/rocketio/controller/helpers.rb', line 160

def flash
  @__flash_proxy__ ||= RocketIO::Flash.new(session)
end

#halt(*args) ⇒ Object

stop executing any code and send response to browser.

accepts an arbitrary number of arguments. if first argument is a Rack::Response, halting right away using the first arg as response and ignore other args.

if first arg is a Array, updating current response using first array element as status, second to update headers and 3rd as body

if some arg is an Integer, it will be used as status code. if some arg is a Hash, it is treated as headers. any other args are treated as body.

if no args given it halts with current response.

Examples:

returning “Well Done” body with 200 status code

halt 'Well Done'

halting with current response without alter it in any way

halt

returning a error message with 500 code:

halt 500, 'Sorry, some fatal error occurred'

custom content type

halt File.read('/path/to/theme.css'), 'Content-Type' => mime_type('.css')

sending custom Rack response

halt [200, {'Content-Disposition' => "attachment; filename=some-file"}, some_IO_instance]

Parameters:

  • *args (Array)


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rocketio/controller/helpers.rb', line 131

def halt *args
  args.each do |a|
    case a
    when Rack::Response
      throw(:__response__, a.finish)
    when Fixnum
      response.status = a
    when Array
      if a.size == 3
        response.status = a[0]
        response.headers.update(a[1])
        response.body = a[2]
        break
      else
        response.body = a
      end
    when Hash
      response.headers.update(a)
    else
      response.body = a
    end
  end
  throw(:__response__, response.finish)
end

#headers(hash = nil) ⇒ Object

Set multiple response headers with Hash.



199
200
201
202
# File 'lib/rocketio/controller/helpers.rb', line 199

def headers hash = nil
  response.headers.merge!(hash) if hash
  response.headers
end

#http_1_1?Boolean

returns true for HTTP/1.1 requests

Returns:

  • (Boolean)


68
69
70
# File 'lib/rocketio/controller/helpers.rb', line 68

def http_1_1?
  env[RocketIO::HTTP_VERSION] == RocketIO::HTTP_1_1
end

#informational?Boolean

whether or not the status is set to 1xx

Returns:

  • (Boolean)


38
39
40
# File 'lib/rocketio/controller/helpers.rb', line 38

def informational?
  response.status.between? 100, 199
end

#invoke_after_filter(method = requested_method) ⇒ Object



138
139
140
141
# File 'lib/rocketio/controller/filters.rb', line 138

def invoke_after_filter method = requested_method
  __send__(after[:*]) if after[:*]
  __send__(after[method]) if after[method]
end

#invoke_around_filter(method = requested_method, block) ⇒ Object

passing blocks tends to add some overhead so passing the proc as a common argument



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/rocketio/controller/filters.rb', line 122

def invoke_around_filter method = requested_method, block
  if around[:*]
    __send__ around[:*], proc {
      if around[method]
        __send__(around[method], block)
      else
        block.call
      end
    }
  elsif around[method]
    __send__(around[method], block)
  else
    block.call
  end
end

#invoke_before_filter(method = requested_method) ⇒ Object



115
116
117
118
# File 'lib/rocketio/controller/filters.rb', line 115

def invoke_before_filter method = requested_method
  __send__(before[:*]) if before[:*]
  __send__(before[method]) if before[method]
end

#json?Boolean

Returns:

  • (Boolean)


76
77
78
79
80
# File 'lib/rocketio/controller/helpers.rb', line 76

def json?
  # using regex because Content-Type may look like "application/json;charset=UTF-8"
  # TODO: consider using Regexp#match? on Ruby 2.4
  env[HTTP_CONTENT_TYPE] =~ RocketIO::APPLICATION_JSON_REGEXP
end

#last_modified(time) ⇒ Object

Set the last modified time of the resource (HTTP ‘Last-Modified’ header) and halt if conditional GET matches. The time argument is a Time, DateTime, or other object that responds to to_time.

When the current request includes an ‘If-Modified-Since’ header that is equal or later than the time specified, execution is immediately halted with a ‘304 Not Modified’ response.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/rocketio/controller/helpers.rb', line 351

def last_modified time
  return unless time
  time = time_for(time)
  response[RocketIO::LAST_MODIFIED] = time.httpdate
  return if env[RocketIO::HTTP_IF_NONE_MATCH]

  if response.ok? && env[RocketIO::HTTP_IF_MODIFIED_SINCE]
    # compare based on seconds since epoch
    since = Time.httpdate(env[RocketIO::HTTP_IF_MODIFIED_SINCE]).to_i
    halt(304) if since >= time.to_i
  end

  if (response.successful? || response.precondition_failed?) && env[RocketIO::HTTP_IF_UNMODIFIED_SINCE]
    # compare based on seconds since epoch
    since = Time.httpdate(env[RocketIO::HTTP_IF_UNMODIFIED_SINCE]).to_i
    halt(412) if since < time.to_i
  end
rescue ArgumentError
end

#middlewareObject



30
# File 'lib/rocketio/controller/middleware.rb', line 30

def middleware; RocketIO::EMPTY_ARRAY end

#not_found?Boolean

whether or not the status is set to 404

Returns:

  • (Boolean)


63
64
65
# File 'lib/rocketio/controller/helpers.rb', line 63

def not_found?
  response.status == 404
end

#paramsObject



86
87
88
# File 'lib/rocketio/controller.rb', line 86

def params
  @__params__ ||= indifferent_params(json? ? __parse_json_body__ : request.params)
end

#pass(controller, *args) ⇒ Object

switch controller and halt with returned response. any arguments will be passed to requested method.

Examples:

pass control to User controller, call requested method without arguments

pass User

pass control to User controller, call requested method with [:bob, :bobsen] arguments

pass User, :bob, :bobsen


95
96
97
# File 'lib/rocketio/controller/helpers.rb', line 95

def pass controller, *args
  halt controller.new(requested_method, args).call(env)
end

#path_paramsObject



76
77
78
79
80
81
82
83
84
# File 'lib/rocketio/controller.rb', line 76

def path_params
  @__path_params__ ||= api[requested_method][:path_params].each_with_object({}) {|(m,r),o|
    o[m] = if r.min && r.max
      r.min == r.max ? path_params_array[r.min] : path_params_array[r]
    else
      path_params_array[r]
    end
  }.freeze
end

#path_params_arrayObject



72
73
74
# File 'lib/rocketio/controller.rb', line 72

def path_params_array
  @__path_params_array__
end

#permanent_redirect(uri) ⇒ Object



174
175
176
177
178
# File 'lib/rocketio/controller/helpers.rb', line 174

def permanent_redirect uri
  response.status = 301
  response[RocketIO::LOCATION] = uri(uri.to_s)
  halt
end

#redirect(uri) ⇒ Object

Halt processing and redirect to the URI provided.



165
166
167
168
169
170
171
172
# File 'lib/rocketio/controller/helpers.rb', line 165

def redirect uri
  response.status = http_1_1? && !get? ? 303 : 302

  # According to RFC 2616 section 14.30, "the field value consists of a
  # single absolute URI"
  response[RocketIO::LOCATION] = uri(uri.to_s)
  halt
end

#redirect?Boolean

whether or not the status is set to 3xx

Returns:

  • (Boolean)


48
49
50
# File 'lib/rocketio/controller/helpers.rb', line 48

def redirect?
  response.status.between? 300, 399
end

#requestObject



90
91
92
# File 'lib/rocketio/controller.rb', line 90

def request
  @__request__ ||= RocketIO::Request.new(env)
end

#request_token_auth(realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM) ⇒ Object



47
48
49
# File 'lib/rocketio/controller/authorization.rb', line 47

def request_token_auth realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM
  RocketIO::TokenAuth.authentication_request(realm)
end

#requested_methodObject



55
56
57
# File 'lib/rocketio/controller.rb', line 55

def requested_method
  @__requested_method__
end

#requested_method_argumentsObject



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rocketio/controller.rb', line 59

def requested_method_arguments
  if api[requested_method][:params_accepted_as_last_argument?]
    [
      *path_params_array,
      # creating a shallow params copy with symbolized keys
      # cause Ruby does not accept string keys for keyword arguments
      params.keys.each_with_object({}) {|k,o| o[k.to_sym] = params[k]}
    ]
  else
    path_params_array
  end
end

#responseObject



94
95
96
# File 'lib/rocketio/controller.rb', line 94

def response
  @__response__ ||= RocketIO::Response.new
end

#send_file(path, opts = {}) ⇒ Object

Use the contents of the file at path as the response body.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/rocketio/controller/helpers.rb', line 264

def send_file path, opts = {}
  if opts[:type] or not response[RocketIO::CONTENT_TYPE]
    content_type(opts[:type] || File.extname(path), default: RocketIO::APPLICATION_OCTET_STREAM)
  end

  disposition = opts[:disposition]
  filename    = opts[:filename]
  disposition = 'attachment' if disposition.nil? and filename
  filename    = path         if filename.nil?
  attachment(filename, disposition) if disposition

  last_modified(opts[:last_modified]) if opts[:last_modified]

  file = Rack::File.new(nil)
  result = file.serving(request, path)
  result[1].each { |k,v| headers[k] ||= v }
  headers[RocketIO::CONTENT_LENGTH] = result[1][RocketIO::CONTENT_LENGTH]
  opts[:status] &&= Integer(opts[:status])
  response.status = opts[:status] || result[0]
  response.body = result[2]
  halt
rescue Errno::ENOENT
  error(404)
end

#server_error?Boolean

whether or not the status is set to 5xx

Returns:

  • (Boolean)


58
59
60
# File 'lib/rocketio/controller/helpers.rb', line 58

def server_error?
  response.status.between? 500, 599
end

#sessionsObject



62
# File 'lib/rocketio/controller/sessions.rb', line 62

def sessions; end

#success?Boolean

whether or not the status is set to 2xx

Returns:

  • (Boolean)


43
44
45
# File 'lib/rocketio/controller/helpers.rb', line 43

def success?
  response.status.between? 200, 299
end

#time_for(value) ⇒ Object

Generates a Time object from the given value. Used by #expires and #last_modified.



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/rocketio/controller/helpers.rb', line 416

def time_for value
  if value.respond_to?(:to_time)
    value.to_time
  elsif value.is_a?(Time)
    value
  elsif value.respond_to?(:new_offset)
    # DateTime#to_time does the same on 1.9
    d = value.new_offset 0
    t = Time.utc(d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction)
    t.getlocal
  elsif value.respond_to?(:mday)
    # Date#to_time does the same on 1.9
    Time.local(value.year, value.mon, value.mday)
  elsif value.is_a? Numeric
    Time.at value
  else
    Time.parse value.to_s
  end
rescue ArgumentError => boom
  raise boom
rescue Exception
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end

#token_authObject



31
# File 'lib/rocketio/controller/authorization.rb', line 31

def token_auth; RocketIO::EMPTY_HASH end

#uri(addr = nil, absolute = true, add_script_name = true) ⇒ Object

Generates the absolute URI for a given path in the app. Takes Rack routers and reverse proxies into account.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rocketio/controller/helpers.rb', line 182

def uri addr = nil, absolute = true, add_script_name = true
  return addr if addr && addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
  uri = [host = ""]
  if absolute
    host << "http#{'s' if request.secure?}://"
    if request.forwarded? or request.port != (request.secure? ? 443 : 80)
      host << request.host_with_port
    else
      host << request.host
    end
  end
  uri << request.script_name.to_s if add_script_name
  uri << (addr ? addr : request.path_info).to_s
  File.join(uri)
end

#user?Boolean

Returns:

  • (Boolean)


108
# File 'lib/rocketio/controller/authentication.rb', line 108

def user?; env[RocketIO::REMOTE_USER] end

#validate_or_request_authentication_if_neededObject

checks whether authentication is required and send an authorization request if credentials not present or invalid



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rocketio/controller/authentication.rb', line 112

def validate_or_request_authentication_if_needed
  return unless auth = authentication_required?
  return unless prompt = auth[:class].new(proc {}, *auth[:arguments]) do |*a|
    self.__send__(auth[:method], *a)
  end.call(
    if RocketIO::HTTP_AUTHORIZATION_KEYS.detect {|key| env.has_key?(key)}
      env
    else
      env.merge(RocketIO::HTTP_AUTHORIZATION_KEYS.first => auth[:mock] % env[RocketIO::PATH_INFO])
    end
  )
  throw(:__response__, prompt)
end

#validate_or_request_authorization_if_neededObject



33
34
35
36
37
# File 'lib/rocketio/controller/authorization.rb', line 33

def validate_or_request_authorization_if_needed
  return unless auth = authorization_required?
  return if validate_token_auth(&auth[:block])
  throw(:__response__, request_token_auth(auth[:realm]))
end

#validate_or_request_token_auth(realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM, &block) ⇒ Object



39
40
41
# File 'lib/rocketio/controller/authorization.rb', line 39

def validate_or_request_token_auth realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM, &block
  validate_token_auth(&block) || request_token_auth(realm)
end

#validate_token_auth(&block) ⇒ Object



43
44
45
# File 'lib/rocketio/controller/authorization.rb', line 43

def validate_token_auth &block
  RocketIO::TokenAuth.authenticate(env, &block)
end

#websocket?Boolean

Returns:

  • (Boolean)


4
5
6
7
# File 'lib/rocketio/controller/websocket.rb', line 4

def websocket?
  @__is_websocket__ ||= websocket_connection? && websocket_upgrade? ? 1 : 0
  @__is_websocket__ == 1
end

#websocket_connection?Boolean

Returns:

  • (Boolean)


9
10
11
# File 'lib/rocketio/controller/websocket.rb', line 9

def websocket_connection?
  env[RocketIO::HTTP_CONNECTION].to_s.downcase.split(/ *, */).include?(RocketIO::UPGRADE)
end

#websocket_responseObject



17
18
19
# File 'lib/rocketio/controller/websocket.rb', line 17

def websocket_response
  [-1, {}, []]
end

#websocket_upgrade?Boolean

Returns:

  • (Boolean)


13
14
15
# File 'lib/rocketio/controller/websocket.rb', line 13

def websocket_upgrade?
  env[RocketIO::HTTP_UPGRADE].to_s.downcase == RocketIO::WEBSOCKET
end

#xhr?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/rocketio/controller/helpers.rb', line 72

def xhr?
  env[RocketIO::HTTP_X_REQUESTED_WITH] == RocketIO::XML_HTTP_REQUEST
end