-
Notifications
You must be signed in to change notification settings - Fork 5
/
rack.rb
189 lines (159 loc) · 7.58 KB
/
rack.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
require 'rack'
require File.join('usher', 'interface', 'rack', 'route')
require File.join('usher', 'interface', 'rack', 'middleware')
require File.join('usher', 'interface', 'rack', 'builder')
class Usher
module Interface
class Rack
ENV_KEY_RESPONSE = 'usher.response'.freeze
ENV_KEY_PARAMS = 'usher.params'.freeze
ENV_KEY_DEFAULT_ROUTER = 'usher.router'.freeze
attr_reader :router, :router_key
attr_accessor :redirect_on_trailing_delimiters
# Constructor for Rack interface for Usher.
# <tt>app</tt> - the default application to route to if no matching route is found. The default is a 404 response.
# <tt>options</tt> - options to configure the router
# * <tt>use_destinations</tt> - option to disable using the destinations passed into routes. (Default <tt>true</tt>)
# * <tt>router_key</tt> - Key in which to put router into env. (Default <tt>usher.router</tt>)
# * <tt>request_methods</tt> - Request methods on <tt>Rack::Request</tt> to use in determining recognition. (Default <tt>[:request_method, :host, :port, :scheme]</tt>)
# * <tt>generator</tt> - Route generator to use. (Default <tt>Usher::Util::Generators::URL.new</tt>)
# * <tt>allow_identical_variable_names</tt> - Option to prevent routes with identical variable names to be added. eg, /:variable/:variable would raise an exception if this option is not enabled. (Default <tt>false</tt>)
def initialize(options = {}, &blk)
@_app = options[:default_app] || proc{|env| ::Rack::Response.new("No route found", 404).finish }
@use_destinations = options.key?(:use_destinations) ? options.delete(:use_destinations) : true
@router_key = options.delete(:router_key) || ENV_KEY_DEFAULT_ROUTER
request_methods = options.delete(:request_methods) || [:request_method, :host, :port, :scheme]
generator = options.delete(:generator) || Usher::Util::Generators::URL.new
allow_identical_variable_names = options.key?(:allow_identical_variable_names) ? options[:allow_identical_variable_names] : false
self.redirect_on_trailing_delimiters = options.key?(:redirect_on_trailing_delimiters) ? options.delete(:redirect_on_trailing_delimiters) : false
if redirect_on_trailing_delimiters
options[:ignore_trailing_delimiters] = true
end
usher_options = {:request_methods => request_methods, :generator => generator, :allow_identical_variable_names => allow_identical_variable_names}
usher_options.merge!(options)
@router = Usher.new(usher_options)
@router.route_class = Rack::Route
instance_eval(&blk) if blk
end
# Returns whether the route set has use_destinations? enabled.
def use_destinations?
@use_destinations
end
# Creates a deep copy of the current route set.
def dup
new_one = super
original = self
new_one.instance_eval do
@router = router.dup
end
new_one
end
# Adds a route to the route set with a +path+ and optional +options+.
# See <tt>Usher#add_route</tt> for more details about the format of the route and options accepted here.
def add(path, options = nil)
@router.add_route(path, options)
end
alias_method :path, :add
# Sets the default application when route matching is unsuccessful. Accepts either an application +app+ or a block to call.
#
# default { |env| ... }
# default DefaultApp
def default(app = nil, &block)
@_app = app ? app : block
end
# shortcuts for adding routes for HTTP methods, for example:
# add("/url", :conditions => {:request_method => "POST"}})
# is the same as:
# post("/url")
# it returns route, and because you may want to work with the route,
# for example give it a name, we returns the route with GET request
# Convenience method for adding a route that only matches request method +GET+.
def only_get(path, options = {})
add(path, options.merge!(:conditions => {:request_method => ["GET"]}))
end
# Convenience method for adding a route that only matches request methods +GET+ and +HEAD+.
def get(path, options = {})
add(path, options.merge!(:conditions => {:request_method => ["HEAD", "GET"]}))
end
# Convenience method for adding a route that only matches request method +POST+.
def post(path, options = {})
add(path, options.merge!(:conditions => {:request_method => "POST"}))
end
# Convenience method for adding a route that only matches request method +PUT+.
def put(path, options = {})
add(path, options.merge!(:conditions => {:request_method => "PUT"}))
end
# Convenience method for adding a route that only matches request method +DELETE+.
def delete(path, options = {})
add(path, options.merge!(:conditions => {:request_method => "DELETE"}))
end
def parent_route=(route)
@router.parent_route = route
end
def parent_route
@router.parent_route
end
def reset!
@router.reset!
end
def call(env)
env[router_key] = self
request = ::Rack::Request.new(env)
response = @router.recognize(request, request.path_info)
if redirect_on_trailing_delimiters and response.only_trailing_delimiters and (request.get? || request.head?)
response = ::Rack::Response.new
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
response.finish
else
after_match(request, response) if response
determine_respondant(response).call(env)
end
end
def generate(route, options = nil)
@router.generator.generate(route, options)
end
# Allows a hook to be placed for sub classes to make use of between matching
# and calling the application
#
# @api plugin
def after_match(request, response)
params = response.path.route.default_values ? response.path.route.default_values.merge(response.params_as_hash) : response.params_as_hash
request.env[ENV_KEY_RESPONSE] ||= []
request.env[ENV_KEY_RESPONSE] << response
request.env[ENV_KEY_PARAMS] ?
request.env[ENV_KEY_PARAMS].merge!(params) :
(request.env[ENV_KEY_PARAMS] = params)
# consume the path_info to the script_name
# response.remaining_path
consume_path!(request, response) if response.partial_match?
end
# Determines which application to respond with.
#
# Within the request when determine respondant is called
# If there is a matching route to an application, that
# application is called, Otherwise the middleware application is called.
#
# @api private
def determine_respondant(response)
usable_response = use_destinations? && response && response.destination
if usable_response && response.destination.respond_to?(:call)
response.destination
elsif usable_response && response.destination.respond_to?(:args) && response.destination.args.first.respond_to?(:call)
response.args.first
else
_app
end
end
# Consume the path from path_info to script_name
def consume_path!(request, response)
request.env["SCRIPT_NAME"] = (request.env["SCRIPT_NAME"] + response.matched_path) || ""
request.env["PATH_INFO"] = response.remaining_path || ""
end
def default_app
_app
end
private
attr_reader :_app
end
end
end