soar_sc-rack-router 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -2
- data/lib/soar_sc/rack/router.rb +27 -12
- data/lib/soar_sc/rack/router/builder_syntax.rb +11 -9
- data/lib/soar_sc/rack/router/http_method.rb +19 -0
- data/lib/soar_sc/rack/router/route.rb +25 -10
- data/lib/soar_sc/rack/router/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06db72006f7280dbcdfd881a108d084e9fb67b79
|
4
|
+
data.tar.gz: 4ffcd46d095e341fce59740dad5a6faaa3ed8552
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1962096dc371cc19ce1d25bde13a0724f9d934006bcf0a0c36036f7bc3b762a2154025effb39aa52aa8f90c2b5a024ddf502e22d8683c3f27376e5bfb38122f2
|
7
|
+
data.tar.gz: c7b0b28b6ec325022474b76bff8194de423beb484b185eba8516d7c9549eaaea0a60c16bbbda5a20793ad883663ca2201af1d38116313a3d877426dd30f7bc50
|
data/README.md
CHANGED
@@ -14,6 +14,11 @@ Because it supports parameterized URLs, it is familiar to users of several Ruby
|
|
14
14
|
into WADL and other service descriptions. The parameter support is compatible with Rack::Request, without pushing a dependency on Rack::Request
|
15
15
|
down into the router action API; the only requirement for router actions is that they are rack apps.
|
16
16
|
|
17
|
+
Users of Ruby web frameworks like Rails and Sinatra may be surprised that double-slashes in request paths are not normalized.
|
18
|
+
In Sinatra, this is actually done by the `rack-protection` middleware, not `sinatra-router`.
|
19
|
+
Users of `SoarSc::Rack::Router` definitely should be using the [rack-protection](https://rubygems.org/gems/rack-protection) middleware
|
20
|
+
above the router in their stacks.
|
21
|
+
|
17
22
|
## Installation
|
18
23
|
|
19
24
|
Add this line to your application's Gemfile:
|
@@ -74,10 +79,12 @@ stack = Rack::Builder.new do
|
|
74
79
|
use Auditing
|
75
80
|
run ->(...)
|
76
81
|
end
|
77
|
-
|
78
|
-
run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['Object Not Found']] }
|
79
82
|
end
|
83
|
+
|
84
|
+
run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['Object Not Found']] }
|
80
85
|
end
|
86
|
+
|
87
|
+
run stack
|
81
88
|
```
|
82
89
|
|
83
90
|
An example of parameterized URL support:
|
@@ -100,6 +107,25 @@ end
|
|
100
107
|
run stack
|
101
108
|
```
|
102
109
|
|
110
|
+
Note that, even though the router doesn't break the rack stack by demanding that routing actions receive a `Rack::Request`,
|
111
|
+
it's recommended that routing actions use `Rack::Request` internally to retrieve path parameters. The router adds them to rack's
|
112
|
+
`env` using `Rack::Request#update_param`, making `Rack::Request#params` the safest way to retrieve them.
|
113
|
+
|
114
|
+
Finally, here is a demonstration that doesn't use `Rack::Builder`, just to clarify that it's not a dependency:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
require 'soar_sc/rack/router'
|
118
|
+
|
119
|
+
index = ->(env) { ... }
|
120
|
+
about = ->(env) { ... }
|
121
|
+
contact = ->(env) { ...]
|
122
|
+
|
123
|
+
run SoarSc::Rack::Router.new(index) do
|
124
|
+
get "/about", about
|
125
|
+
get "/contact", contact
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
103
129
|
## Development
|
104
130
|
|
105
131
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/soar_sc/rack/router.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "soar_sc/rack/router/builder_syntax"
|
2
2
|
require "soar_sc/rack/router/errors"
|
3
|
+
require "soar_sc/rack/router/http_method"
|
3
4
|
require "soar_sc/rack/router/route"
|
4
5
|
require "soar_sc/rack/router/version"
|
5
6
|
|
@@ -11,18 +12,17 @@ module SoarSc
|
|
11
12
|
|
12
13
|
include BuilderSyntax
|
13
14
|
|
15
|
+
EMPTY_STRING = ""
|
16
|
+
|
14
17
|
def initialize(app, routes = {}, &block)
|
15
18
|
@app = app
|
16
|
-
@
|
17
|
-
|
18
|
-
routes.each { |(p, a)| add_route(Route.new('*', p, a)) }
|
19
|
+
@routes = []
|
20
|
+
routes.each { |(p, a)| add_route(Route.new(HttpMethod::ANY, p, a)) }
|
19
21
|
instance_eval(&block) if block_given?
|
20
22
|
end
|
21
23
|
|
22
24
|
def call(env)
|
23
|
-
if route = @
|
24
|
-
route.action.call(env)
|
25
|
-
elsif route = @parameterized_routes.detect { |r| r.matches?(env) }
|
25
|
+
if route = @routes.detect { |r| r.matches?(env) }
|
26
26
|
update_path_parameters!(env, route.extract_path_parameters(env))
|
27
27
|
route.action.call(env)
|
28
28
|
else
|
@@ -34,18 +34,33 @@ module SoarSc
|
|
34
34
|
|
35
35
|
# Called by BuilderSyntax methods
|
36
36
|
def add_route(route)
|
37
|
-
if (
|
37
|
+
if include?(route)
|
38
38
|
raise DuplicateRouteError.for_route(route)
|
39
|
-
elsif route.static?
|
40
|
-
@static_routes << route
|
41
39
|
else
|
42
|
-
@
|
40
|
+
@routes << route
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def include?(route)
|
45
|
+
@routes.detect do |other|
|
46
|
+
route.matches?(env_for_route(other)) or other.matches?(env_for_route(route))
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
50
|
+
# Creates a minimal env that would be matched by the given route.
|
51
|
+
def env_for_route(route)
|
52
|
+
{
|
53
|
+
Route::REQUEST_METHOD => (route.method == HttpMethod::ANY ? HttpMethod::GET : route.method),
|
54
|
+
Route::SCRIPT_NAME => route.path,
|
55
|
+
Route::PATH_INFO => EMPTY_STRING
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
46
59
|
def update_path_parameters!(env, params)
|
47
|
-
|
48
|
-
|
60
|
+
unless params.empty?
|
61
|
+
req = ::Rack::Request.new(env)
|
62
|
+
params.each { |p, v| req.update_param(p, v) }
|
63
|
+
end
|
49
64
|
end
|
50
65
|
|
51
66
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'soar_sc/rack/router/http_method'
|
2
|
+
|
1
3
|
module SoarSc
|
2
4
|
module Rack
|
3
5
|
class Router
|
@@ -5,39 +7,39 @@ module SoarSc
|
|
5
7
|
module BuilderSyntax
|
6
8
|
|
7
9
|
def map(path, app)
|
8
|
-
add_route(Route.new(
|
10
|
+
add_route(Route.new(HttpMethod::ANY, path, app))
|
9
11
|
end
|
10
12
|
|
11
13
|
def get(path, app)
|
12
|
-
add_route(Route.new(
|
14
|
+
add_route(Route.new(HttpMethod::GET, path, app))
|
13
15
|
end
|
14
16
|
|
15
17
|
def post(path, app)
|
16
|
-
add_route(Route.new(
|
18
|
+
add_route(Route.new(HttpMethod::POST, path, app))
|
17
19
|
end
|
18
20
|
|
19
21
|
def put(path, app)
|
20
|
-
add_route(Route.new(
|
22
|
+
add_route(Route.new(HttpMethod::PUT, path, app))
|
21
23
|
end
|
22
24
|
|
23
25
|
def delete(path, app)
|
24
|
-
add_route(Route.new(
|
26
|
+
add_route(Route.new(HttpMethod::DELETE, path, app))
|
25
27
|
end
|
26
28
|
|
27
29
|
def options(path, app)
|
28
|
-
add_route(Route.new(
|
30
|
+
add_route(Route.new(HttpMethod::OPTIONS, path, app))
|
29
31
|
end
|
30
32
|
|
31
33
|
def head(path, app)
|
32
|
-
add_route(Route.new(
|
34
|
+
add_route(Route.new(HttpMethod::HEAD, path, app))
|
33
35
|
end
|
34
36
|
|
35
37
|
def trace(path, app)
|
36
|
-
add_route(Route.new(
|
38
|
+
add_route(Route.new(HttpMethod::TRACE, path, app))
|
37
39
|
end
|
38
40
|
|
39
41
|
def connect(path, app)
|
40
|
-
add_route(Route.new(
|
42
|
+
add_route(Route.new(HttpMethod::CONNECT, path, app))
|
41
43
|
end
|
42
44
|
|
43
45
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SoarSc
|
2
|
+
module Rack
|
3
|
+
class Router
|
4
|
+
module HttpMethod
|
5
|
+
|
6
|
+
ANY = "*"
|
7
|
+
GET = "GET"
|
8
|
+
POST = "POST"
|
9
|
+
PUT = "PUT"
|
10
|
+
DELETE = "DELETE"
|
11
|
+
HEAD = "HEAD"
|
12
|
+
OPTIONS = "OPTIONS"
|
13
|
+
TRACE = "TRACE"
|
14
|
+
CONNECT = "CONNECT"
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,35 +1,50 @@
|
|
1
|
+
require 'soar_sc/rack/router/http_method'
|
2
|
+
|
3
|
+
# TODO Normalize paths (compress runs of slashes)
|
1
4
|
module SoarSc
|
2
5
|
module Rack
|
3
6
|
class Router
|
4
7
|
|
5
8
|
class Route
|
9
|
+
PARAMETER_PREFIX = ":"
|
10
|
+
PARAMETER_VALID_REGEXP = /:[a-z_][a-z0-9_]*/
|
11
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
12
|
+
SCRIPT_NAME = "SCRIPT_NAME"
|
13
|
+
PATH_INFO = "PATH_INFO"
|
14
|
+
EMPTY_HASH = {}.freeze
|
15
|
+
|
6
16
|
attr_reader :method, :path, :action
|
7
17
|
|
8
18
|
def initialize(method, path, action)
|
9
19
|
validate_path(path)
|
10
20
|
@method, @path, @action = method, path, action
|
11
|
-
|
12
|
-
|
13
|
-
def static?
|
14
|
-
components(path).none? { |c| c.start_with?(":") }
|
21
|
+
@static = components(path).none? { |c| c.start_with?(PARAMETER_PREFIX) }
|
15
22
|
end
|
16
23
|
|
17
24
|
def matches?(env)
|
18
|
-
return false unless
|
25
|
+
return false unless method == env[REQUEST_METHOD] or method == HttpMethod::ANY
|
26
|
+
|
27
|
+
return request_path(env) == path if @static
|
19
28
|
|
29
|
+
route_components = components(path)
|
20
30
|
req_components = components(request_path(env))
|
21
|
-
|
22
|
-
|
31
|
+
|
32
|
+
return false unless route_components.size == req_components.size
|
33
|
+
|
34
|
+
route_components.each_with_index do |c, i|
|
35
|
+
return false unless c.start_with?(PARAMETER_PREFIX) or c == req_components[i]
|
23
36
|
end
|
24
37
|
|
25
38
|
return true
|
26
39
|
end
|
27
40
|
|
28
41
|
def extract_path_parameters(env)
|
42
|
+
return EMPTY_HASH if @static
|
43
|
+
|
29
44
|
req_components = components(request_path(env))
|
30
45
|
parameters = {}
|
31
46
|
components(path).each_with_index do |c, i|
|
32
|
-
parameters[c[1..-1]] = req_components[i] if c.start_with?(
|
47
|
+
parameters[c[1..-1]] = req_components[i] if c.start_with?(PARAMETER_PREFIX)
|
33
48
|
end
|
34
49
|
parameters
|
35
50
|
end
|
@@ -37,7 +52,7 @@ module SoarSc
|
|
37
52
|
private
|
38
53
|
|
39
54
|
def request_path(env)
|
40
|
-
env[
|
55
|
+
env[SCRIPT_NAME] + env[PATH_INFO]
|
41
56
|
end
|
42
57
|
|
43
58
|
def components(path)
|
@@ -46,7 +61,7 @@ module SoarSc
|
|
46
61
|
|
47
62
|
def validate_path(path)
|
48
63
|
components(path).each do |c|
|
49
|
-
if c.start_with?(
|
64
|
+
if c.start_with?(PARAMETER_PREFIX) and c !~ PARAMETER_VALID_REGEXP
|
50
65
|
raise InvalidParameterError, "Invalid parameter #{c} in path #{path}"
|
51
66
|
end
|
52
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: soar_sc-rack-router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sheldon Hearn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- lib/soar_sc/rack/router.rb
|
87
87
|
- lib/soar_sc/rack/router/builder_syntax.rb
|
88
88
|
- lib/soar_sc/rack/router/errors.rb
|
89
|
+
- lib/soar_sc/rack/router/http_method.rb
|
89
90
|
- lib/soar_sc/rack/router/route.rb
|
90
91
|
- lib/soar_sc/rack/router/version.rb
|
91
92
|
- soar_sc-rack-router.gemspec
|