rocketio 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -5
- data/.pryrc +2 -0
- data/.travis.yml +3 -0
- data/README.md +22 -5
- data/Rakefile +7 -1
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/rocketio.rb +131 -3
- data/lib/rocketio/application.rb +31 -0
- data/lib/rocketio/controller.rb +288 -0
- data/lib/rocketio/controller/authentication.rb +141 -0
- data/lib/rocketio/controller/authorization.rb +53 -0
- data/lib/rocketio/controller/cookies.rb +59 -0
- data/lib/rocketio/controller/error_handlers.rb +89 -0
- data/lib/rocketio/controller/filters.rb +119 -0
- data/lib/rocketio/controller/flash.rb +21 -0
- data/lib/rocketio/controller/helpers.rb +438 -0
- data/lib/rocketio/controller/middleware.rb +32 -0
- data/lib/rocketio/controller/render.rb +148 -0
- data/lib/rocketio/controller/render/engine.rb +76 -0
- data/lib/rocketio/controller/render/layout.rb +27 -0
- data/lib/rocketio/controller/render/layouts.rb +85 -0
- data/lib/rocketio/controller/render/templates.rb +83 -0
- data/lib/rocketio/controller/request.rb +115 -0
- data/lib/rocketio/controller/response.rb +84 -0
- data/lib/rocketio/controller/sessions.rb +64 -0
- data/lib/rocketio/controller/token_auth.rb +118 -0
- data/lib/rocketio/controller/websocket.rb +21 -0
- data/lib/rocketio/error_templates/404.html +3 -0
- data/lib/rocketio/error_templates/409.html +7 -0
- data/lib/rocketio/error_templates/500.html +3 -0
- data/lib/rocketio/error_templates/501.html +6 -0
- data/lib/rocketio/error_templates/layout.html +1 -0
- data/lib/rocketio/exceptions.rb +4 -0
- data/lib/rocketio/router.rb +65 -0
- data/lib/rocketio/util.rb +122 -0
- data/lib/rocketio/version.rb +2 -2
- data/rocketio.gemspec +21 -17
- data/test/aliases_test.rb +54 -0
- data/test/authentication_test.rb +307 -0
- data/test/authorization_test.rb +91 -0
- data/test/cache_control_test.rb +268 -0
- data/test/content_type_test.rb +124 -0
- data/test/cookies_test.rb +49 -0
- data/test/error_handlers_test.rb +125 -0
- data/test/etag_test.rb +445 -0
- data/test/filters_test.rb +177 -0
- data/test/halt_test.rb +73 -0
- data/test/helpers_test.rb +171 -0
- data/test/middleware_test.rb +57 -0
- data/test/redirect_test.rb +135 -0
- data/test/render/engine_test.rb +71 -0
- data/test/render/get.erb +1 -0
- data/test/render/items.erb +1 -0
- data/test/render/layout.erb +1 -0
- data/test/render/layout_test.rb +104 -0
- data/test/render/layouts/master.erb +1 -0
- data/test/render/layouts_test.rb +145 -0
- data/test/render/master.erb +1 -0
- data/test/render/post.erb +1 -0
- data/test/render/put.erb +1 -0
- data/test/render/render_test.rb +101 -0
- data/test/render/setup.rb +14 -0
- data/test/render/templates/a/get.erb +1 -0
- data/test/render/templates/master.erb +1 -0
- data/test/render/templates_test.rb +146 -0
- data/test/request_test.rb +105 -0
- data/test/response_test.rb +119 -0
- data/test/routes_test.rb +70 -0
- data/test/sendfile_test.rb +209 -0
- data/test/sessions_test.rb +176 -0
- data/test/setup.rb +59 -0
- metadata +144 -9
- data/LICENSE.txt +0 -22
@@ -0,0 +1,115 @@
|
|
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 Request < Rack::Request
|
28
|
+
|
29
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
|
30
|
+
HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
|
31
|
+
|
32
|
+
# Returns an array of acceptable media types for the response
|
33
|
+
def accept
|
34
|
+
@__accept__ ||= begin
|
35
|
+
if env.include?(HTTP_ACCEPT) and !(accept = env[HTTP_ACCEPT].to_s).empty?
|
36
|
+
accept.scan(HEADER_VALUE_WITH_PARAMS).map! {|e| AcceptEntry.new(e)}.sort
|
37
|
+
else
|
38
|
+
[AcceptEntry.new('*/*')]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def accept?(type)
|
44
|
+
preferred_type(type).to_s.include?(type)
|
45
|
+
end
|
46
|
+
|
47
|
+
def preferred_type(*types)
|
48
|
+
accepts = accept # just evaluate once
|
49
|
+
return accepts.first if types.empty?
|
50
|
+
types.flatten!
|
51
|
+
return types.first if accepts.empty?
|
52
|
+
accepts.detect do |pattern|
|
53
|
+
type = types.detect { |t| File.fnmatch(pattern, t) }
|
54
|
+
return type if type
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
alias secure? ssl?
|
59
|
+
|
60
|
+
def forwarded?
|
61
|
+
env.include?(HTTP_X_FORWARDED_HOST)
|
62
|
+
end
|
63
|
+
|
64
|
+
def safe?
|
65
|
+
get? or head? or options? or trace?
|
66
|
+
end
|
67
|
+
|
68
|
+
def idempotent?
|
69
|
+
safe? or put? or delete? or link? or unlink?
|
70
|
+
end
|
71
|
+
|
72
|
+
class AcceptEntry
|
73
|
+
attr_accessor :params
|
74
|
+
attr_reader :entry
|
75
|
+
|
76
|
+
def initialize(entry)
|
77
|
+
params = entry.scan(HEADER_PARAM).map! do |s|
|
78
|
+
key, value = s.strip.split('=', 2)
|
79
|
+
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
|
80
|
+
[key, value]
|
81
|
+
end
|
82
|
+
|
83
|
+
@entry = entry
|
84
|
+
@type = entry[/[^;]+/].delete(' ')
|
85
|
+
@params = Hash[params]
|
86
|
+
@q = @params.delete('q') { 1.0 }.to_f
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=>(other)
|
90
|
+
other.priority <=> self.priority
|
91
|
+
end
|
92
|
+
|
93
|
+
def priority
|
94
|
+
# We sort in descending order; better matches should be higher.
|
95
|
+
[ @q, -@type.count('*'), @params.size ]
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_str
|
99
|
+
@type
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s(full = false)
|
103
|
+
full ? entry : to_str
|
104
|
+
end
|
105
|
+
|
106
|
+
def respond_to?(*args)
|
107
|
+
super or to_str.respond_to?(*args)
|
108
|
+
end
|
109
|
+
|
110
|
+
def method_missing(*args, &block)
|
111
|
+
to_str.send(*args, &block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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 Response < Rack::Response
|
28
|
+
|
29
|
+
def body= value
|
30
|
+
@body = case value
|
31
|
+
when Rack::Response
|
32
|
+
value.body
|
33
|
+
when Proc
|
34
|
+
def value.each; yield(call) end
|
35
|
+
value
|
36
|
+
when String
|
37
|
+
[value]
|
38
|
+
else
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def empty_body?
|
44
|
+
@body == EMPTY_ARRAY || @body.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def each
|
48
|
+
block_given? ? super : enum_for(:each)
|
49
|
+
end
|
50
|
+
|
51
|
+
def finish
|
52
|
+
headers[CONTENT_TYPE] ||= DEFAULT_CONTENT_TYPE
|
53
|
+
|
54
|
+
if drop_content_info?
|
55
|
+
headers.delete(CONTENT_LENGTH)
|
56
|
+
headers.delete(CONTENT_TYPE)
|
57
|
+
end
|
58
|
+
|
59
|
+
if drop_body?
|
60
|
+
close
|
61
|
+
self.body = EMPTY_ARRAY
|
62
|
+
end
|
63
|
+
|
64
|
+
[status.to_i, headers, self.body]
|
65
|
+
end
|
66
|
+
|
67
|
+
def precondition_failed?
|
68
|
+
status.to_i == 412
|
69
|
+
end
|
70
|
+
|
71
|
+
def not_modified?
|
72
|
+
status.to_i == 304
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def drop_content_info?
|
77
|
+
status.to_i / 100 == 1 || drop_body?
|
78
|
+
end
|
79
|
+
|
80
|
+
def drop_body?
|
81
|
+
DROP_BODY_RESPONSES[status.to_i]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Controller
|
3
|
+
|
4
|
+
# setup sessions.
|
5
|
+
# there are 2 setups out of the box:
|
6
|
+
# - cookies
|
7
|
+
# - memcache
|
8
|
+
# you can also use your own setup
|
9
|
+
#
|
10
|
+
# @note to disable sessions for some controller set sessions to nil or false
|
11
|
+
#
|
12
|
+
# @example keep sessions in cookies
|
13
|
+
# sessions :cookies
|
14
|
+
#
|
15
|
+
# @example keep sessions in cookies using custom options
|
16
|
+
# sessions :cookies, secret: 'someSecret', domain: 'some.domain'
|
17
|
+
#
|
18
|
+
# @example keep sessions in memcache
|
19
|
+
# sessions :memcache
|
20
|
+
#
|
21
|
+
# @example use a custom setup, i.e. github.com/migrs/rack-session-mongo
|
22
|
+
# $ gem install rack-session-mongo
|
23
|
+
#
|
24
|
+
# require 'rack/session/mongo'
|
25
|
+
#
|
26
|
+
# class Pages < RocketIO::Controller
|
27
|
+
# sessions Rack::Session::Mongo
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example disable sessions for current controller
|
31
|
+
# sessions nil # or false
|
32
|
+
#
|
33
|
+
# @param pool [Symbol, Class]
|
34
|
+
# @param opts [Hash]
|
35
|
+
#
|
36
|
+
def self.sessions pool = (noargs = true; nil), opts = {}
|
37
|
+
@__sessions__ = if pool.nil? || pool == false
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
if pool == :cookies && opts[:secret].nil?
|
41
|
+
opts[:secret] = ::Digest::MD5.hexdigest([Time.now.to_f, rand]*'')
|
42
|
+
end
|
43
|
+
pool = case pool
|
44
|
+
when :cookies
|
45
|
+
::Rack::Session::Cookie
|
46
|
+
when :memcache
|
47
|
+
::Rack::Session::Memcache
|
48
|
+
else
|
49
|
+
pool
|
50
|
+
end
|
51
|
+
[pool, opts]
|
52
|
+
end
|
53
|
+
define_sessions_methods
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.define_sessions_methods source = self
|
57
|
+
return unless source.instance_variables.include?(:@__sessions__)
|
58
|
+
sessions = source.instance_variable_get(:@__sessions__)
|
59
|
+
define_method(:sessions) {sessions}
|
60
|
+
end
|
61
|
+
|
62
|
+
def sessions; end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Copyright (c) 2004-2015 David Heinemeier Hansson
|
2
|
+
# Copyright (c) 2015 Slee Woo
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RocketIO
|
24
|
+
module TokenAuth
|
25
|
+
extend self
|
26
|
+
|
27
|
+
TOKEN_KEY = 'token='.freeze
|
28
|
+
TOKEN_REGEX = /^Token /
|
29
|
+
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
|
30
|
+
|
31
|
+
HTTP_AUTHORIZATION = 'HTTP_AUTHORIZATION'.freeze
|
32
|
+
X_HTTP_AUTHORIZATION_I = 'X-HTTP_AUTHORIZATION'.freeze
|
33
|
+
X_HTTP_AUTHORIZATION_II = 'X_HTTP_AUTHORIZATION'.freeze
|
34
|
+
REDIRECT_X_HTTP_AUTHORIZATION = 'REDIRECT_X_HTTP_AUTHORIZATION'.freeze
|
35
|
+
|
36
|
+
WWW_AUTHENTICATE = 'WWW-Authenticate'.freeze
|
37
|
+
TOKEN_REALM_FORMAT = 'Token realm="%s"'.freeze
|
38
|
+
ACCESS_DENIED = "HTTP Token: Access denied.\n".freeze
|
39
|
+
|
40
|
+
# If token Authorization header is present, call the login
|
41
|
+
# procedure with the present token and options.
|
42
|
+
#
|
43
|
+
# @return the return value of given block if a token is found
|
44
|
+
# @return nil if no token is found
|
45
|
+
def authenticate env
|
46
|
+
token, options = token_and_options(env)
|
47
|
+
return if token.nil? || token.empty?
|
48
|
+
yield(token, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parses the token and options out of the token authorization header. If
|
52
|
+
# the header looks like this:
|
53
|
+
# Authorization: Token token="abc", nonce="def"
|
54
|
+
# Then the returned token is "abc", and the options is {nonce: "def"}
|
55
|
+
#
|
56
|
+
# @return [Array] if a token is present
|
57
|
+
# @return nil if no token is found
|
58
|
+
def token_and_options env
|
59
|
+
return unless authorization_request = authorization?(env)
|
60
|
+
return unless authorization_request[TOKEN_REGEX]
|
61
|
+
params = token_params_from(authorization_request)
|
62
|
+
[params.shift[1], RocketIO.indifferent_params(Hash[params])]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the authorization header regardless of whether it was specified directly or through one of the
|
66
|
+
# proxy alternatives.
|
67
|
+
def authorization? env
|
68
|
+
env[HTTP_AUTHORIZATION] ||
|
69
|
+
env[X_HTTP_AUTHORIZATION_I] ||
|
70
|
+
env[X_HTTP_AUTHORIZATION_II] ||
|
71
|
+
env[REDIRECT_X_HTTP_AUTHORIZATION]
|
72
|
+
end
|
73
|
+
|
74
|
+
def token_params_from auth
|
75
|
+
rewrite_param_values(params_array_from(raw_params(auth)))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Takes raw_params and turns it into an array of parameters
|
79
|
+
#
|
80
|
+
# @param raw_params [Array]
|
81
|
+
# @return [Array]
|
82
|
+
def params_array_from raw_params
|
83
|
+
raw_params.map { |param| param.split %r/=(.+)?/ }
|
84
|
+
end
|
85
|
+
|
86
|
+
# This removes the `"` characters wrapping the value.
|
87
|
+
#
|
88
|
+
# @param array_params [Array]
|
89
|
+
def rewrite_param_values array_params
|
90
|
+
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
|
91
|
+
end
|
92
|
+
|
93
|
+
# This method takes an authorization body and splits up the key-value
|
94
|
+
# pairs by the standardized `:`, `;`, or `\t`
|
95
|
+
#
|
96
|
+
# @param auth [String]
|
97
|
+
# @return [Array]
|
98
|
+
def raw_params auth
|
99
|
+
_raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
|
100
|
+
unless _raw_params.first =~ %r{\A#{TOKEN_KEY}}
|
101
|
+
_raw_params[0] = [TOKEN_KEY, _raw_params.first]*''
|
102
|
+
end
|
103
|
+
_raw_params
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sets a WWW-Authenticate to let the client know a token is desired.
|
107
|
+
#
|
108
|
+
# @param realm [String]
|
109
|
+
# @return [Array]
|
110
|
+
def authentication_request realm
|
111
|
+
[
|
112
|
+
401,
|
113
|
+
{WWW_AUTHENTICATE => TOKEN_REALM_FORMAT % realm.tr('"', '')},
|
114
|
+
[ACCESS_DENIED]
|
115
|
+
]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Controller
|
3
|
+
|
4
|
+
def websocket?
|
5
|
+
@__is_websocket__ ||= websocket_connection? && websocket_upgrade? ? 1 : 0
|
6
|
+
@__is_websocket__ == 1
|
7
|
+
end
|
8
|
+
|
9
|
+
def websocket_connection?
|
10
|
+
env[RocketIO::HTTP_CONNECTION].to_s.downcase.split(/ *, */).include?(RocketIO::UPGRADE)
|
11
|
+
end
|
12
|
+
|
13
|
+
def websocket_upgrade?
|
14
|
+
env[RocketIO::HTTP_UPGRADE].to_s.downcase == RocketIO::WEBSOCKET
|
15
|
+
end
|
16
|
+
|
17
|
+
def websocket_response
|
18
|
+
[-1, {}, []]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{{{ yield }}}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Router
|
3
|
+
ROOT_PATH_PIECES = [''.freeze].freeze
|
4
|
+
attr_reader :controllers, :routes
|
5
|
+
|
6
|
+
def initialize *controllers
|
7
|
+
@controllers = controllers.flatten.compact.uniq
|
8
|
+
@routes = build_routes
|
9
|
+
freeze!
|
10
|
+
end
|
11
|
+
|
12
|
+
def resolve_path path
|
13
|
+
controllers = @routes
|
14
|
+
pieces = path_pieces(path)
|
15
|
+
depth = -1
|
16
|
+
controller = nil
|
17
|
+
offset = 0
|
18
|
+
while controllers = controllers[pieces[depth += 1]]
|
19
|
+
next unless controllers[0]
|
20
|
+
controller = controllers[0]
|
21
|
+
offset = depth + 1
|
22
|
+
end
|
23
|
+
[controller, pieces[offset .. -1]]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# split given path into an array
|
28
|
+
#
|
29
|
+
# @example /a/b/c
|
30
|
+
# ["", a", "b", "c"]
|
31
|
+
#
|
32
|
+
# @param path [String]
|
33
|
+
# @return [Array]
|
34
|
+
#
|
35
|
+
def path_pieces path
|
36
|
+
return ROOT_PATH_PIECES if path == SLASH # that's root path, /
|
37
|
+
path.to_s.split(/\/+/)
|
38
|
+
end
|
39
|
+
|
40
|
+
# building a routing tree
|
41
|
+
#
|
42
|
+
# @return [Hash]
|
43
|
+
#
|
44
|
+
def build_routes
|
45
|
+
map = {}
|
46
|
+
@controllers.each do |controller|
|
47
|
+
[controller.url, *controller.aliases].each do |url|
|
48
|
+
depth = 0
|
49
|
+
pieces = path_pieces(url)
|
50
|
+
pieces.inject(map) do |map,chunk|
|
51
|
+
map[chunk] ||= {}
|
52
|
+
map[chunk][0] = controller if pieces.size == (depth += 1)
|
53
|
+
map[chunk]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
map
|
58
|
+
end
|
59
|
+
|
60
|
+
# freezing the application cause modifying it at runtime may blow the universe
|
61
|
+
def freeze!
|
62
|
+
self.freeze
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|