1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Routes with parameters.
19 # Using `vararg_routes`, a `Route` path can contain variable parts
20 # that will be matched against a `HttpRequest` URL.
22 # Variable parameters of a route path can be specified using the `:` prefix:
25 # var iface = "http://localhost:3000"
26 # var vh = new VirtualHost(iface)
27 # vh.routes.add new Route("/blog/articles/:articleId", new BlogArticleAction)
30 # Route arguments can be accessed from the `HttpRequest` within a nitcorn `Action`:
33 # class BlogArticleAction
36 # redef fun answer(request, url) do
37 # var param = request.param("articleId")
38 # if param == null then
39 # return new HttpResponse(400)
42 # print url # let's say "/blog/articles/12"
45 # return new HttpResponse(200)
52 # Route can match variables expression.
55 # # We need an Action to try routes.
56 # class DummyAction super Action end
57 # var action = new DummyAction
59 # var route = new Route("/users/:id", action)
60 # assert not route.match("/users")
61 # assert route.match("/users/1234")
62 # assert route.match("/users/") # empty id
65 # Route without uri parameters still behave like before.
68 # route = new Route("/users", action)
69 # assert route.match("/users")
70 # assert route.match("/users/1234")
71 # assert not route.match("/issues/1234")
76 # Priority depends on the order the routes were added to the `Routes` dispatcher.
79 # var host = new VirtualHost("")
80 # var routes = new Routes(host)
82 # routes.add new Route("/:a/:b/:c", action)
83 # routes.add new Route("/users/:id", action)
84 # routes.add new Route("/:foo", action)
86 # assert routes["/a/b/c"].path == "/:a/:b/:c"
87 # assert routes["/a/b/c/d"].path == "/:a/:b/:c"
88 # assert routes["/users/1234/foo"].path == "/:a/:b/:c"
90 # assert routes["/users/"].path == "/users/:id"
91 # assert routes["users/"].path == "/users/:id"
92 # assert routes["/users/1234"].path == "/users/:id"
94 # assert routes["/users"].path == "/:foo"
95 # assert routes["/"].path == "/:foo"
96 # assert routes[""].path == "/:foo"
99 # ## Accessing uri parameter and values
101 # Parameters can be accessed by parsing the uri.
104 # route = new Route("/users/:id", action)
105 # var params = route.parse_params("/users/1234")
106 # assert params.has_key("id")
107 # assert not params.has_key("foo")
108 # assert params["id"] == "1234"
111 # Or from the `HttpRequest`.
114 # route = new Route("/users/:id", action)
115 # var req = new HttpRequest
116 # req.uri_params = route.parse_params("/users/1234")
117 # assert req.params == ["id"]
118 # assert req.param("id") == "1234"
119 # assert req.param("foo") == null
126 # A route to an `Action` according to a `path`
134 # Replace `self.path` parameters with concrete values from the `request` URI.
135 fun resolve_path
(request
: HttpRequest): nullable String do
136 if pattern_parts
.is_empty
then return self.path
138 for part
in pattern_parts
do
139 if part
isa UriString then
141 else if part
isa UriParam then
142 path
/= request
.param
(part
.name
) or else part
.name
148 # Cut `path` into `UriParts`.
149 private fun parse_pattern
(path
: nullable String) do
150 if path
== null then return
151 path
= standardize_path
(path
)
152 var parts
= path
.split
("/")
154 if not part
.is_empty
and part
.first
== ':' then
156 var name
= part
.substring
(1, part
.length
)
157 var param
= new UriParam(name
)
158 pattern_parts
.add param
160 # is a standard string
161 pattern_parts
.add
new UriString(part
)
166 # `UriPart` forming `self` pattern.
167 private var pattern_parts
= new Array[UriPart]
169 # Does `self` matches `uri`?
170 fun match
(uri
: nullable String): Bool do
171 if pattern_parts
.is_empty
then return true
172 if uri
== null then return false
173 uri
= standardize_path
(uri
)
174 var parts
= uri
.split
("/")
175 for i
in [0 .. pattern_parts
.length
[ do
176 if i
>= parts
.length
then return false
177 var ppart
= pattern_parts
[i
]
179 if not ppart
.match
(part
) then return false
184 # Extract parameter values from `uri`.
185 fun parse_params
(uri
: nullable String): Map[String, String] do
186 var res
= new HashMap[String, String]
187 if pattern_parts
.is_empty
then return res
188 if uri
== null then return res
189 uri
= standardize_path
(uri
)
190 var parts
= uri
.split
("/")
191 for i
in [0 .. pattern_parts
.length
[ do
192 if i
>= parts
.length
then return res
193 var ppart
= pattern_parts
[i
]
195 if not ppart
.match
(part
) then return res
196 if ppart
isa UriParam then
197 res
[ppart
.name
] = part
203 # Remove first occurence of `/`.
204 private fun standardize_path
(path
: String): String do
205 if not path
.is_empty
and path
.first
== '/' then
206 return path
.substring
(1, path
.length
)
212 # A String that compose an URI.
214 # In practice, UriPart can be parameters or static strings.
215 private interface UriPart
216 # Does `self` matches a part of the uri?
217 fun match
(uri_part
: String): Bool is abstract
220 # An uri parameter string like `:id`.
221 private class UriParam
224 # Param `name` in the route uri.
227 # Parameters match everything.
228 redef fun match
(part
) do return true
230 redef fun to_s
do return name
233 # A static uri string like `users`.
234 private class UriString
240 # Empty strings match everything otherwise matching is based on string equality.
241 redef fun match
(part
) do return string
.is_empty
or string
== part
243 redef fun to_s
do return string
247 # Use `Route::match` instead of `==`.
249 for route
in routes
do
250 if route
.match
(key
) then return route
256 redef class HttpRequest
258 # Parameters found in uri associated to their values.
259 var uri_params
: Map[String, String] = new HashMap[String, String] is public
writable
261 # Get the value for parameter `name` or `null`.
262 fun param
(name
: String): nullable String do
263 if not uri_params
.has_key
(name
) then return null
264 return uri_params
[name
]
267 # List all uri parameters matched by this request.
268 fun params
: Array[String] do return uri_params
.keys
.to_a