Class: Rack::JetRouter
- Inherits:
-
Object
- Object
- Rack::JetRouter
- Defined in:
- lib/rack/jet_router.rb
Overview
Jet-speed router class, derived from Keight.rb.
Example #1:
### (assume that 'xxxx_app' are certain Rack applications.)
mapping = {
"/" => home_app,
"/api" => {
"/books" => {
"" => books_app,
"/:id(.:format)" => book_app,
"/:book_id/comments/:comment_id" => comment_app,
},
},
"/admin" => {
"/books" => admin_books_app,
},
}
router = Rack::JetRouter.new(mapping)
router.lookup("/api/books/123.html")
#=> [book_app, {"id"=>"123", "format"=>"html"}]
status, headers, body = router.call(env)
Example #2:
mapping = [
["/" , {GET: home_app}],
["/api", [
["/books", [
["" , {GET: book_list_app, POST: book_create_app}],
["/:id(.:format)" , {GET: book_show_app, PUT: book_update_app}],
["/:book_id/comments/:comment_id", {POST: comment_create_app}],
]],
]],
["/admin", [
["/books" , {ANY: admin_books_app}],
]],
]
router = Rack::JetRouter.new(mapping)
router.lookup("/api/books/123")
#=> [{"GET"=>book_show_app, "PUT"=>book_update_app}, {"id"=>"123", "format"=>nil}]
status, headers, body = router.call(env)
Example #3:
mapping = {
"/" => {GET: home_app}, # not {"GET"=>home_app}
"/api" => {
"/books" => { # not {"GET"=>..., "POST"=>...}
"" => {GET: book_list_app, POST: book_create_app},
"/:id(.:format)" => {GET: book_show_app, PUT: book_update_app},
"/:book_id/comments/:comment_id" => {POST: comment_create_app},
},
},
"/admin" => {
"/books" => {ANY: admin_books_app}, # not {"ANY"=>...}
},
}
router = Rack::JetRouter.new(mapping)
router.lookup("/api/books/123")
#=> [{"GET"=>book_show_app, "PUT"=>book_update_app}, {"id"=>"123", "format"=>nil}]
status, headers, body = router.call(env)
Defined Under Namespace
Classes: Builder
Constant Summary collapse
- RELEASE =
'$Release: 1.4.0 $'.split()[1]
- REQUEST_METHODS =
; [!haggu] contains available request methods.
%w[GET POST PUT DELETE PATCH HEAD OPTIONS TRACE LINK UNLINK] \ .each_with_object({}) {|s, d| d[s] = s.intern }
Instance Attribute Summary collapse
-
#urlpath_rexp ⇒ Object
readonly
Returns the value of attribute urlpath_rexp.
Instance Method Summary collapse
-
#call(env) ⇒ Object
Finds rack app according to PATH_INFO and REQUEST_METHOD and invokes it.
-
#each(&block) ⇒ Object
Yields pair of urlpath pattern and app.
-
#initialize(mapping, cache_size: 0, env_key: 'rack.urlpath_params', int_param: nil, urlpath_cache_size: 0, _enable_range: true) ⇒ JetRouter
constructor
A new instance of JetRouter.
-
#lookup(req_path) ⇒ Object
(also: #find)
Finds app or Hash mapped to request path.
-
#normalize_method_mapping(dict) ⇒ Object
called from Builder class.
-
#param2rexp(param) ⇒ Object
Returns regexp string of path parameter.
Constructor Details
#initialize(mapping, cache_size: 0, env_key: 'rack.urlpath_params', int_param: nil, urlpath_cache_size: 0, _enable_range: true) ⇒ JetRouter
Returns a new instance of JetRouter.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/rack/jet_router.rb', line 85 def initialize(mapping, cache_size: 0, env_key: 'rack.urlpath_params', int_param: nil, # ex: /(?:\A|_)id\z/ urlpath_cache_size: 0, # for backward compatibility _enable_range: true) # undocumentend keyword arg @env_key = env_key @int_param = int_param #; [!21mf9] 'urlpath_cache_size:' kwarg is available for backward compatibility. @cache_size = [cache_size, urlpath_cache_size].max() #; [!5tw57] cache is disabled when 'cache_size:' is zero. @cache_dict = @cache_size > 0 ? {} : nil ## ## Pair list of endpoint and Rack app. ## ex: ## [ ## ["/api/books" , books_app ], ## ["/api/books/:id" , book_app ], ## ["/api/orders" , orders_app], ## ["/api/orders/:id" , order_app ], ## ] ## @all_endpoints = [] ## ## Endpoints without any path parameters. ## ex: ## { ## "/" => home_app, ## "/api/books" => books_app, ## "/api/orders" => orders_app, ## } ## @fixed_endpoints = {} ## ## Endpoints with one or more path parameters. ## ex: ## [ ## [%r!\A/api/books/([^./?]+)\z! , ["id"], book_app , (11..-1)], ## [%r!\A/api/orders/([^./?]+)\z!, ["id"], order_app, (12..-1)], ## ] ## @variable_endpoints = [] ## ## Combined regexp of variable endpoints. ## ex: ## %r!\A/api/(?:books/[^./?]+(\z)|orders/[^./?]+(\z))\z! ## @urlpath_rexp = nil # #; [!x2l32] gathers all endpoints. builder = Builder.new(self, _enable_range) param_rexp = /[:*]\w+|\(.*?\)/ tmplist = [] builder.traverse_mapping(mapping) do |path, item| @all_endpoints << [path, item] #; [!l63vu] handles urlpath pattern as fixed when no urlpath params. if path !~ param_rexp @fixed_endpoints[path] = item #; [!ec0av] treats '/foo(.html|.json)' as three fixed urlpaths. #; [!ylyi0] stores '/foo' as fixed path when path pattern is '/foo(.:format)'. elsif path =~ /\A([^:*\(\)]*)\(([^\(\)]+)\)\z/ @fixed_endpoints[$1] = item unless $1.empty? arr = [] $2.split('|').each do |s| next if s.empty? if s.include?(':') arr << s else @fixed_endpoints[$1 + s] = item end end tmplist << ["#{$1}(#{arr.join('|')})", item] unless arr.empty? else tmplist << [path, item] end end #; [!saa1a] compiles compound urlpath regexp. tree = builder.build_tree(tmplist) @urlpath_rexp = builder.build_rexp(tree) do |tuple| #; [!f1d7s] builds variable endpoint list. @variable_endpoints << tuple end end |
Instance Attribute Details
#urlpath_rexp ⇒ Object (readonly)
Returns the value of attribute urlpath_rexp.
167 168 169 |
# File 'lib/rack/jet_router.rb', line 167 def urlpath_rexp @urlpath_rexp end |
Instance Method Details
#call(env) ⇒ Object
Finds rack app according to PATH_INFO and REQUEST_METHOD and invokes it.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/rack/jet_router.rb', line 170 def call(env) #; [!fpw8x] finds mapped app according to env['PATH_INFO']. req_path = env['PATH_INFO'] obj, param_values = lookup(req_path) #; [!wxt2g] guesses correct urlpath and redirects to it automaticaly when request path not found. #; [!3vsua] doesn't redict automatically when request path is '/'. if ! obj && should_redirect?(env) location = req_path.end_with?("/") ? req_path[0..-2] : req_path + "/" obj, param_values = lookup(location) if obj #; [!hyk62] adds QUERY_STRING to redirect location. qs = env['QUERY_STRING'] location = "#{location}?#{qs}" if qs && ! qs.empty? return redirect_to(location) end end #; [!30x0k] returns 404 when request urlpath not found. return error_not_found(env) unless obj #; [!gclbs] if mapped object is a Hash... if obj.is_a?(Hash) #; [!p1fzn] invokes app mapped to request method. #; [!5m64a] returns 405 when request method is not allowed. #; [!ys1e2] uses GET method when HEAD is not mapped. #; [!2hx6j] try ANY method when request method is not mapped. dict = obj req_meth = env['REQUEST_METHOD'] app = dict[req_meth] || (req_meth == 'HEAD' ? dict['GET'] : nil) || dict['ANY'] return error_not_allowed(env) unless app else app = obj end #; [!2c32f] stores urlpath parameter values into env['rack.urlpath_params']. store_param_values(env, param_values) #; [!hse47] invokes app mapped to request urlpath. return app.call(env) # make body empty when HEAD? end |
#each(&block) ⇒ Object
Yields pair of urlpath pattern and app.
251 252 253 254 |
# File 'lib/rack/jet_router.rb', line 251 def each(&block) #; [!ep0pw] yields pair of urlpath pattern and app. @all_endpoints.each(&block) end |
#lookup(req_path) ⇒ Object Also known as: find
Finds app or Hash mapped to request path.
ex:
lookup('/api/books/123') #=> [BookApp, {"id"=>"123"}]
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/rack/jet_router.rb', line 211 def lookup(req_path) #; [!24khb] finds in fixed urlpaths at first. #; [!iwyzd] urlpath param value is nil when found in fixed urlpaths. obj = @fixed_endpoints[req_path] return obj, nil if obj #; [!upacd] finds in variable urlpath cache if it is enabled. #; [!1zx7t] variable urlpath cache is based on LRU. cache = @cache_dict if cache && (pair = cache.delete(req_path)) cache[req_path] = pair return pair end #; [!vpdzn] returns nil when urlpath not found. m = @urlpath_rexp.match(req_path) return nil unless m index = m.captures.index('') return nil unless index #; [!ijqws] returns mapped object and urlpath parameter values when urlpath found. full_urlpath_rexp, param_names, obj, range, sep = @variable_endpoints[index] if range ## "/books/123"[7..-1] is faster than /\A\/books\/(\d+)\z/.match("/books/123")[1] str = req_path[range] ## `"/a/1/b/2"[3..-1].split('/b/')` is faster than `%r!\A/a/(\d+)/b/(\d+)\z!.match("/a/1/b/2").captures` values = sep ? str.split(sep) : [str] else m = full_urlpath_rexp.match(req_path) values = m.captures end param_values = build_param_values(param_names, values) #; [!84inr] caches result when variable urlpath cache enabled. if cache cache.shift() if cache.length >= @cache_size cache[req_path] = [obj, param_values] end return obj, param_values end |
#normalize_method_mapping(dict) ⇒ Object
called from Builder class
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/rack/jet_router.rb', line 315 def normalize_method_mapping(dict) # called from Builder class #; [!r7cmk] converts keys into string. #; [!z9kww] allows 'ANY' as request method. #; [!k7sme] raises error when unknown request method specified. #; [!itfsd] returns new Hash object. #; [!gd08f] if arg is an instance of Hash subclass, returns new instance of it. request_methods = REQUEST_METHODS #newdict = {} newdict = dict.class.new dict.each do |meth_sym, app| meth_str = meth_sym.to_s request_methods[meth_str] || meth_str == 'ANY' or raise ArgumentError.new("#{meth_sym}: unknown request method.") newdict[meth_str] = app end return newdict end |
#param2rexp(param) ⇒ Object
Returns regexp string of path parameter. Override if necessary.
334 335 336 337 338 |
# File 'lib/rack/jet_router.rb', line 334 def param2rexp(param) # called from Builder class #; [!6sd9b] returns regexp string according to param name. #; [!rfvk2] returns '\d+' if param name matched to int param regexp. return (rexp = @int_param) && rexp.match?(param) ? '\d+' : '[^./?]+' end |