hanami-router 0.0.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -0
- data/LICENSE.md +22 -0
- data/README.md +667 -9
- data/hanami-router.gemspec +18 -12
- data/lib/hanami-router.rb +1 -0
- data/lib/hanami/router.rb +1160 -3
- data/lib/hanami/router/version.rb +3 -2
- data/lib/hanami/routing/endpoint.rb +151 -0
- data/lib/hanami/routing/endpoint_resolver.rb +225 -0
- data/lib/hanami/routing/error.rb +7 -0
- data/lib/hanami/routing/force_ssl.rb +209 -0
- data/lib/hanami/routing/http_router.rb +187 -0
- data/lib/hanami/routing/namespace.rb +92 -0
- data/lib/hanami/routing/parsers.rb +71 -0
- data/lib/hanami/routing/parsing/json_parser.rb +28 -0
- data/lib/hanami/routing/parsing/parser.rb +58 -0
- data/lib/hanami/routing/recognized_route.rb +153 -0
- data/lib/hanami/routing/resource.rb +116 -0
- data/lib/hanami/routing/resource/action.rb +387 -0
- data/lib/hanami/routing/resource/nested.rb +39 -0
- data/lib/hanami/routing/resource/options.rb +74 -0
- data/lib/hanami/routing/resources.rb +48 -0
- data/lib/hanami/routing/resources/action.rb +150 -0
- data/lib/hanami/routing/route.rb +62 -0
- data/lib/hanami/routing/routes_inspector.rb +215 -0
- metadata +94 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'hanami/routing/error'
|
3
|
+
require 'hanami/utils/class'
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Routing
|
7
|
+
# Endpoint not found
|
8
|
+
# This is raised when the router fails to load an endpoint at the runtime.
|
9
|
+
#
|
10
|
+
# @since 0.1.0
|
11
|
+
class EndpointNotFound < Hanami::Routing::Error
|
12
|
+
end
|
13
|
+
|
14
|
+
# Routing endpoint
|
15
|
+
# This is the object that responds to an HTTP request made against a certain
|
16
|
+
# path.
|
17
|
+
#
|
18
|
+
# The router will use this class for:
|
19
|
+
#
|
20
|
+
# * Procs and any Rack compatible object (respond to #call)
|
21
|
+
#
|
22
|
+
# @since 0.1.0
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# require 'hanami/router'
|
28
|
+
#
|
29
|
+
# Hanami::Router.new do
|
30
|
+
# get '/proc', to: ->(env) { [200, {}, ['This will use Hanami::Routing::Endpoint']] }
|
31
|
+
# get '/rack-app', to: RackApp.new
|
32
|
+
# end
|
33
|
+
class Endpoint < SimpleDelegator
|
34
|
+
# @since 0.2.0
|
35
|
+
def inspect
|
36
|
+
case __getobj__
|
37
|
+
when Proc
|
38
|
+
source, line = __getobj__.source_location
|
39
|
+
lambda_inspector = " (lambda)" if __getobj__.lambda?
|
40
|
+
|
41
|
+
"#<Proc@#{ ::File.expand_path(source) }:#{ line }#{ lambda_inspector }>"
|
42
|
+
when Class
|
43
|
+
__getobj__
|
44
|
+
else
|
45
|
+
"#<#{ __getobj__.class }>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Routing endpoint
|
51
|
+
# This is the object that responds to an HTTP request made against a certain
|
52
|
+
# path.
|
53
|
+
#
|
54
|
+
# The router will use this class for:
|
55
|
+
#
|
56
|
+
# * Classes
|
57
|
+
# * Hanami::Action endpoints referenced as a class
|
58
|
+
# * Hanami::Action endpoints referenced a string
|
59
|
+
# * RESTful resource(s)
|
60
|
+
#
|
61
|
+
# @since 0.1.0
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# require 'hanami/router'
|
67
|
+
#
|
68
|
+
# Hanami::Router.new do
|
69
|
+
# get '/class', to: RackMiddleware
|
70
|
+
# get '/hanami-action-class', to: Dashboard::Index
|
71
|
+
# get '/hanami-action-string', to: 'dashboard#index'
|
72
|
+
#
|
73
|
+
# resource 'identity'
|
74
|
+
# resources 'articles'
|
75
|
+
# end
|
76
|
+
class ClassEndpoint < Endpoint
|
77
|
+
# Rack interface
|
78
|
+
#
|
79
|
+
# @since 0.1.0
|
80
|
+
def call(env)
|
81
|
+
__getobj__.new.call(env)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Routing endpoint
|
86
|
+
# This is the object that responds to an HTTP request made against a certain
|
87
|
+
# path.
|
88
|
+
#
|
89
|
+
# The router will use this class for the same use cases of `ClassEndpoint`,
|
90
|
+
# but when the target class can't be found, instead of raise a `LoadError`
|
91
|
+
# we reference in a lazy endpoint.
|
92
|
+
#
|
93
|
+
# For each incoming HTTP request, it will look for the referenced class,
|
94
|
+
# then it will instantiate and invoke #call on the object.
|
95
|
+
#
|
96
|
+
# This behavior is required to solve a chicken-egg situation when we try
|
97
|
+
# to load the router first and then the application with all its endpoints.
|
98
|
+
#
|
99
|
+
# @since 0.1.0
|
100
|
+
#
|
101
|
+
# @api private
|
102
|
+
#
|
103
|
+
# @see Hanami::Routing::ClassEndpoint
|
104
|
+
class LazyEndpoint < Endpoint
|
105
|
+
# Initialize the lazy endpoint
|
106
|
+
#
|
107
|
+
# @since 0.1.0
|
108
|
+
def initialize(name, namespace)
|
109
|
+
@name, @namespace = name, namespace
|
110
|
+
end
|
111
|
+
|
112
|
+
# Rack interface
|
113
|
+
#
|
114
|
+
# @raise [EndpointNotFound] when the endpoint can't be found.
|
115
|
+
#
|
116
|
+
# @since 0.1.0
|
117
|
+
def call(env)
|
118
|
+
obj.call(env)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @since 0.2.0
|
122
|
+
def inspect
|
123
|
+
# TODO review this implementation once the namespace feature will be
|
124
|
+
# cleaned up.
|
125
|
+
result = klass rescue nil
|
126
|
+
|
127
|
+
if result.nil?
|
128
|
+
result = @name
|
129
|
+
result = "#{ @namespace }::#{ result }" if @namespace != Object
|
130
|
+
end
|
131
|
+
|
132
|
+
result
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
# @since 0.1.0
|
137
|
+
# @api private
|
138
|
+
def obj
|
139
|
+
klass.new
|
140
|
+
end
|
141
|
+
|
142
|
+
# @since 0.2.0
|
143
|
+
# @api private
|
144
|
+
def klass
|
145
|
+
Utils::Class.load!(@name, @namespace)
|
146
|
+
rescue NameError => e
|
147
|
+
raise EndpointNotFound.new(e.message)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'hanami/utils/string'
|
2
|
+
require 'hanami/utils/class'
|
3
|
+
require 'hanami/routing/endpoint'
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Routing
|
7
|
+
# Resolve duck-typed endpoints
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class EndpointResolver
|
13
|
+
# @since 0.2.0
|
14
|
+
# @api private
|
15
|
+
NAMING_PATTERN = '%{controller}::%{action}'.freeze
|
16
|
+
|
17
|
+
# Default separator for controller and action.
|
18
|
+
# A different separator can be passed to #initialize with the `:separator` option.
|
19
|
+
#
|
20
|
+
# @see #initialize
|
21
|
+
# @see #resolve
|
22
|
+
#
|
23
|
+
# @since 0.1.0
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# require 'hanami/router'
|
27
|
+
#
|
28
|
+
# router = Hanami::Router.new do
|
29
|
+
# get '/', to: 'articles#show'
|
30
|
+
# end
|
31
|
+
ACTION_SEPARATOR = '#'.freeze
|
32
|
+
|
33
|
+
attr_reader :action_separator
|
34
|
+
|
35
|
+
# Initialize an endpoint resolver
|
36
|
+
#
|
37
|
+
# @param options [Hash] the options used to customize lookup behavior
|
38
|
+
#
|
39
|
+
# @option options [Class] :endpoint the endpoint class that is returned
|
40
|
+
# by `#resolve`. (defaults to `Hanami::Routing::Endpoint`)
|
41
|
+
#
|
42
|
+
# @option options [Class,Module] :namespace the Ruby namespace where to
|
43
|
+
# lookup for controllers and actions. (defaults to `Object`)
|
44
|
+
#
|
45
|
+
# @option options [String] :pattern the string to interpolate in order
|
46
|
+
# to return an action name. This string SHOULD contain
|
47
|
+
# <tt>'%{controller}'</tt> and <tt>'%{action}'</tt>, all the other keys
|
48
|
+
# will be ignored.
|
49
|
+
# See the examples below.
|
50
|
+
#
|
51
|
+
# @option options [String] :action_separator the sepatator between controller and
|
52
|
+
# action name. (defaults to `ACTION_SEPARATOR`)
|
53
|
+
#
|
54
|
+
# @return [Hanami::Routing::EndpointResolver] self
|
55
|
+
#
|
56
|
+
# @since 0.1.0
|
57
|
+
#
|
58
|
+
# @example Specify custom endpoint class
|
59
|
+
# require 'hanami/router'
|
60
|
+
#
|
61
|
+
# resolver = Hanami::Routing::EndpointResolver.new(endpoint: CustomEndpoint)
|
62
|
+
# router = Hanami::Router.new(resolver: resolver)
|
63
|
+
#
|
64
|
+
# router.get('/', to: endpoint).dest # => #<CustomEndpoint:0x007f97f3359570 ...>
|
65
|
+
#
|
66
|
+
# @example Specify custom Ruby namespace
|
67
|
+
# require 'hanami/router'
|
68
|
+
#
|
69
|
+
# resolver = Hanami::Routing::EndpointResolver.new(namespace: MyApp)
|
70
|
+
# router = Hanami::Router.new(resolver: resolver)
|
71
|
+
#
|
72
|
+
# router.get('/', to: 'articles#show')
|
73
|
+
# # => Will look for: MyApp::Articles::Show
|
74
|
+
#
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# @example Specify custom pattern
|
78
|
+
# require 'hanami/router'
|
79
|
+
#
|
80
|
+
# resolver = Hanami::Routing::EndpointResolver.new(pattern: '%{controller}Controller::%{action}')
|
81
|
+
# router = Hanami::Router.new(resolver: resolver)
|
82
|
+
#
|
83
|
+
# router.get('/', to: 'articles#show')
|
84
|
+
# # => Will look for: ArticlesController::Show
|
85
|
+
#
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# @example Specify custom controller-action separator
|
89
|
+
# require 'hanami/router'
|
90
|
+
#
|
91
|
+
# resolver = Hanami::Routing::EndpointResolver.new(separator: '@')
|
92
|
+
# router = Hanami::Router.new(resolver: resolver)
|
93
|
+
#
|
94
|
+
# router.get('/', to: 'articles@show')
|
95
|
+
# # => Will look for: Articles::Show
|
96
|
+
def initialize(options = {})
|
97
|
+
@endpoint_class = options[:endpoint] || Endpoint
|
98
|
+
@namespace = options[:namespace] || Object
|
99
|
+
@action_separator = options[:action_separator] || ACTION_SEPARATOR
|
100
|
+
@pattern = options[:pattern] || NAMING_PATTERN
|
101
|
+
end
|
102
|
+
|
103
|
+
# Resolve the given set of HTTP verb, path, endpoint and options.
|
104
|
+
# If it fails to resolve, it will mount the default endpoint to the given
|
105
|
+
# path, which returns an 404 (Not Found).
|
106
|
+
#
|
107
|
+
# @param options [Hash] the options required to resolve the endpoint
|
108
|
+
#
|
109
|
+
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
110
|
+
# @option options [String] :namespace an optional routing namespace
|
111
|
+
#
|
112
|
+
# @return [Endpoint] this may vary according to the :endpoint option
|
113
|
+
# passed to #initialize
|
114
|
+
#
|
115
|
+
# @since 0.1.0
|
116
|
+
#
|
117
|
+
# @see #initialize
|
118
|
+
# @see #find
|
119
|
+
#
|
120
|
+
# @example Resolve to a Proc
|
121
|
+
# require 'hanami/router'
|
122
|
+
#
|
123
|
+
# router = Hanami::Router.new
|
124
|
+
# router.get '/', to: ->(env) { [200, {}, ['Hi!']] }
|
125
|
+
#
|
126
|
+
# @example Resolve to a class
|
127
|
+
# require 'hanami/router'
|
128
|
+
#
|
129
|
+
# router = Hanami::Router.new
|
130
|
+
# router.get '/', to: RackMiddleware
|
131
|
+
#
|
132
|
+
# @example Resolve to a Rack compatible object (respond to #call)
|
133
|
+
# require 'hanami/router'
|
134
|
+
#
|
135
|
+
# router = Hanami::Router.new
|
136
|
+
# router.get '/', to: AnotherMiddleware.new
|
137
|
+
#
|
138
|
+
# @example Resolve to a Hanami::Action from a string (see Hanami::Controller framework)
|
139
|
+
# require 'hanami/router'
|
140
|
+
#
|
141
|
+
# router = Hanami::Router.new
|
142
|
+
# router.get '/', to: 'articles#show'
|
143
|
+
#
|
144
|
+
# @example Resolve to a Hanami::Action (see Hanami::Controller framework)
|
145
|
+
# require 'hanami/router'
|
146
|
+
#
|
147
|
+
# router = Hanami::Router.new
|
148
|
+
# router.get '/', to: Articles::Show
|
149
|
+
#
|
150
|
+
# @example Resolve a redirect with a namespace
|
151
|
+
# require 'hanami/router'
|
152
|
+
#
|
153
|
+
# router = Hanami::Router.new
|
154
|
+
# router.namespace 'users' do
|
155
|
+
# get '/home', to: ->(env) { ... }
|
156
|
+
# redirect '/dashboard', to: '/home'
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# # GET /users/dashboard => 301 Location: "/users/home"
|
160
|
+
def resolve(options, &endpoint)
|
161
|
+
result = endpoint || find(options)
|
162
|
+
resolve_callable(result) || resolve_matchable(result) || default
|
163
|
+
end
|
164
|
+
|
165
|
+
# Finds a path from the given options.
|
166
|
+
#
|
167
|
+
# @param options [Hash] the path description
|
168
|
+
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
169
|
+
# @option options [String] :namespace an optional namespace
|
170
|
+
#
|
171
|
+
# @since 0.1.0
|
172
|
+
#
|
173
|
+
# @return [Object]
|
174
|
+
def find(options)
|
175
|
+
options[:to]
|
176
|
+
end
|
177
|
+
|
178
|
+
protected
|
179
|
+
def default
|
180
|
+
@endpoint_class.new(
|
181
|
+
->(env) { [404, {'X-Cascade' => 'pass'}, 'Not Found'] }
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def constantize(string)
|
186
|
+
klass = Utils::Class.load!(string, @namespace)
|
187
|
+
if klass.respond_to?(:call)
|
188
|
+
Endpoint.new(klass)
|
189
|
+
else
|
190
|
+
ClassEndpoint.new(klass)
|
191
|
+
end
|
192
|
+
rescue NameError
|
193
|
+
LazyEndpoint.new(string, @namespace)
|
194
|
+
end
|
195
|
+
|
196
|
+
def classify(string)
|
197
|
+
Utils::String.new(string).classify
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
def resolve_callable(callable)
|
202
|
+
if callable.respond_to?(:call)
|
203
|
+
@endpoint_class.new(callable)
|
204
|
+
elsif callable.is_a?(Class) && callable.instance_methods.include?(:call)
|
205
|
+
@endpoint_class.new(callable.new)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def resolve_matchable(matchable)
|
210
|
+
if matchable.respond_to?(:match)
|
211
|
+
constantize(
|
212
|
+
resolve_action(matchable) || classify(matchable)
|
213
|
+
)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def resolve_action(string)
|
218
|
+
if string.match(action_separator)
|
219
|
+
controller, action = string.split(action_separator).map {|token| classify(token) }
|
220
|
+
@pattern % {controller: controller, action: action}
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Routing
|
5
|
+
# Force ssl
|
6
|
+
#
|
7
|
+
# Redirect response to the secure equivalent resource (https)
|
8
|
+
#
|
9
|
+
# @since 0.4.1
|
10
|
+
# @api private
|
11
|
+
class ForceSsl
|
12
|
+
# Https scheme
|
13
|
+
#
|
14
|
+
# @since 0.4.1
|
15
|
+
# @api private
|
16
|
+
SSL_SCHEME = 'https'.freeze
|
17
|
+
|
18
|
+
# @since 0.4.1
|
19
|
+
# @api private
|
20
|
+
HTTPS = 'HTTPS'.freeze
|
21
|
+
|
22
|
+
# @since 0.4.1
|
23
|
+
# @api private
|
24
|
+
ON = 'on'.freeze
|
25
|
+
|
26
|
+
# Location header
|
27
|
+
#
|
28
|
+
# @since 0.4.1
|
29
|
+
# @api private
|
30
|
+
LOCATION_HEADER = 'Location'.freeze
|
31
|
+
|
32
|
+
# Default http port
|
33
|
+
#
|
34
|
+
# @since 0.4.1
|
35
|
+
# @api private
|
36
|
+
DEFAULT_HTTP_PORT = 80
|
37
|
+
|
38
|
+
# Default ssl port
|
39
|
+
#
|
40
|
+
# @since 0.4.1
|
41
|
+
# @api private
|
42
|
+
DEFAULT_SSL_PORT = 443
|
43
|
+
|
44
|
+
# Moved Permanently http code
|
45
|
+
#
|
46
|
+
# @since 0.4.1
|
47
|
+
# @api private
|
48
|
+
MOVED_PERMANENTLY_HTTP_CODE = 301
|
49
|
+
|
50
|
+
# Temporary Redirect http code
|
51
|
+
#
|
52
|
+
# @since 0.4.1
|
53
|
+
# @api private
|
54
|
+
TEMPORARY_REDIRECT_HTTP_CODE = 307
|
55
|
+
|
56
|
+
# @since 0.4.1
|
57
|
+
# @api private
|
58
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
|
59
|
+
|
60
|
+
# @since 0.4.1
|
61
|
+
# @api private
|
62
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
|
63
|
+
|
64
|
+
# @since 0.4.1
|
65
|
+
# @api private
|
66
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
|
67
|
+
|
68
|
+
# @since 0.4.1
|
69
|
+
# @api private
|
70
|
+
HTTP_X_FORWARDED_PROTO_SEPARATOR = ','.freeze
|
71
|
+
|
72
|
+
# @since 0.4.1
|
73
|
+
# @api private
|
74
|
+
RACK_URL_SCHEME = 'rack.url_scheme'.freeze
|
75
|
+
|
76
|
+
# @since 0.4.1
|
77
|
+
# @api private
|
78
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
79
|
+
|
80
|
+
# @since 0.4.1
|
81
|
+
# @api private
|
82
|
+
IDEMPOTENT_METHODS = ['GET', 'HEAD'].freeze
|
83
|
+
|
84
|
+
EMPTY_BODY = [].freeze
|
85
|
+
|
86
|
+
# Initialize ForceSsl.
|
87
|
+
#
|
88
|
+
# @param active [Boolean] activate redirection to SSL
|
89
|
+
# @param options [Hash] set of options
|
90
|
+
# @option options [String] :host
|
91
|
+
# @option options [Integer] :port
|
92
|
+
#
|
93
|
+
# @since 0.4.1
|
94
|
+
# @api private
|
95
|
+
def initialize(active, options = {})
|
96
|
+
@active = active
|
97
|
+
@host = options[:host]
|
98
|
+
@port = options[:port]
|
99
|
+
|
100
|
+
_redefine_call
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set 301 status and Location header if this feature is activated.
|
104
|
+
#
|
105
|
+
# @param env [Hash] a Rack env instance
|
106
|
+
#
|
107
|
+
# @return [Array]
|
108
|
+
#
|
109
|
+
# @see Hanami::Routing::HttpRouter#call
|
110
|
+
#
|
111
|
+
# @since 0.4.1
|
112
|
+
# @api private
|
113
|
+
def call(env)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Check if router has to force the response with ssl
|
117
|
+
#
|
118
|
+
# @return [Boolean]
|
119
|
+
#
|
120
|
+
# @since 0.4.1
|
121
|
+
# @api private
|
122
|
+
def force?(env)
|
123
|
+
!ssl?(env)
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# @since 0.4.1
|
129
|
+
# @api private
|
130
|
+
attr_reader :host
|
131
|
+
|
132
|
+
# Return full url to redirect
|
133
|
+
#
|
134
|
+
# @param env [Hash] Rack env
|
135
|
+
#
|
136
|
+
# @return [String]
|
137
|
+
#
|
138
|
+
# @since 0.4.1
|
139
|
+
# @api private
|
140
|
+
def full_url(env)
|
141
|
+
"#{ SSL_SCHEME }://#{ host }:#{ port }#{ ::Rack::Request.new(env).fullpath }"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return redirect code
|
145
|
+
#
|
146
|
+
# @param env [Hash] Rack env
|
147
|
+
#
|
148
|
+
# @return [Integer]
|
149
|
+
#
|
150
|
+
# @since 0.4.1
|
151
|
+
# @api private
|
152
|
+
def redirect_code(env)
|
153
|
+
if IDEMPOTENT_METHODS.include?(env[REQUEST_METHOD])
|
154
|
+
MOVED_PERMANENTLY_HTTP_CODE
|
155
|
+
else
|
156
|
+
TEMPORARY_REDIRECT_HTTP_CODE
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return correct default port for full url
|
161
|
+
#
|
162
|
+
# @return [Integer]
|
163
|
+
#
|
164
|
+
# @since 0.4.1
|
165
|
+
# @api private
|
166
|
+
def port
|
167
|
+
if @port == DEFAULT_HTTP_PORT
|
168
|
+
DEFAULT_SSL_PORT
|
169
|
+
else
|
170
|
+
@port
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# @since 0.4.1
|
175
|
+
# @api private
|
176
|
+
def _redefine_call
|
177
|
+
return unless @active
|
178
|
+
|
179
|
+
define_singleton_method :call do |env|
|
180
|
+
[redirect_code(env), { LOCATION_HEADER => full_url(env) }, EMPTY_BODY] if force?(env)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Adapted from Rack::Request#scheme
|
185
|
+
#
|
186
|
+
# @since 0.4.1
|
187
|
+
# @api private
|
188
|
+
def scheme(env)
|
189
|
+
if env[HTTPS] == ON
|
190
|
+
SSL_SCHEME
|
191
|
+
elsif env[HTTP_X_FORWARDED_SSL] == ON
|
192
|
+
SSL_SCHEME
|
193
|
+
elsif env[HTTP_X_FORWARDED_SCHEME]
|
194
|
+
env[HTTP_X_FORWARDED_SCHEME]
|
195
|
+
elsif env[HTTP_X_FORWARDED_PROTO]
|
196
|
+
env[HTTP_X_FORWARDED_PROTO].split(HTTP_X_FORWARDED_PROTO_SEPARATOR)[0]
|
197
|
+
else
|
198
|
+
env[RACK_URL_SCHEME]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# @since 0.4.1
|
203
|
+
# @api private
|
204
|
+
def ssl?(env)
|
205
|
+
scheme(env) == SSL_SCHEME
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|