forked from PerfectlySoft/Perfect
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRouting.swift
More file actions
380 lines (320 loc) · 11.7 KB
/
Copy pathRouting.swift
File metadata and controls
380 lines (320 loc) · 11.7 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//
// Routing.swift
// PerfectLib
//
// Created by Kyle Jessup on 2015-12-11.
// Copyright © 2015 PerfectlySoft. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version, as supplemented by the
// Perfect Additional Terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License, as supplemented by the
// Perfect Additional Terms, for more details.
//
// You should have received a copy of the GNU Affero General Public License
// and the Perfect Additional Terms that immediately follow the terms and
// conditions of the GNU Affero General Public License along with this
// program. If not, see <http://www.perfect.org/AGPL_3_0_With_Perfect_Additional_Terms.txt>.
//
public typealias RequestHandlerGenerator = PageHandlerRegistry.RequestHandlerGenerator
/// Holds the registered routes.
public struct RouteMap: CustomStringConvertible {
/// Pretty prints all route information.
public var description: String {
var s = self.root.description
for (method, root) in self.methodRoots {
s.appendContentsOf("\n" + method + ":\n" + root.description)
}
return s
}
private var root = RouteNode() // root node for any request method
private var methodRoots = [String:RouteNode]() // by convention, use all upper cased method names for inserts/lookups
/// Lookup a route based on the URL path.
/// Returns the handler generator if found.
subscript(path: String, webResponse: WebResponse) -> RequestHandlerGenerator? {
get {
let components = path.lowercaseString.pathComponents
var g = components.generate()
let _ = g.next() // "/"
let method = webResponse.request.requestMethod().uppercaseString
if let root = self.methodRoots[method] {
if let handler = root.findHandler("", generator: g, webResponse: webResponse) {
return handler
}
}
return self.root.findHandler("", generator: g, webResponse: webResponse)
}
}
/// Add a route to the system.
/// `Routing.Routes["/foo/*/baz"] = { _ in return ExampleHandler() }`
public subscript(path: String) -> RequestHandlerGenerator? {
get {
return nil // Swift does not currently allow set-only subscripts
}
set {
self.root.addPathSegments(path.lowercaseString.pathComponents.generate(), h: newValue!)
}
}
/// Add an array of routes for a given handler.
/// `Routing.Routes[ ["/", "index.html"] ] = { _ in return ExampleHandler() }`
public subscript(paths: [String]) -> RequestHandlerGenerator? {
get {
return nil
}
set {
for path in paths {
self[path] = newValue
}
}
}
/// Add a route to the system using the indicated HTTP request method.
/// `Routing.Routes["GET", "/foo/*/baz"] = { _ in return ExampleHandler() }`
public subscript(method: String, path: String) -> RequestHandlerGenerator? {
get {
return nil // Swift does not currently allow set-only subscripts
}
set {
let uppered = method.uppercaseString
if let root = self.methodRoots[uppered] {
root.addPathSegments(path.lowercaseString.pathComponents.generate(), h: newValue!)
} else {
let root = RouteNode()
self.methodRoots[uppered] = root
root.addPathSegments(path.lowercaseString.pathComponents.generate(), h: newValue!)
}
}
}
/// Add an array of routes for a given handler using the indicated HTTP request method.
/// `Routing.Routes["GET", ["/", "index.html"] ] = { _ in return ExampleHandler() }`
public subscript(method: String, paths: [String]) -> RequestHandlerGenerator? {
get {
return nil // Swift does not currently allow set-only subscripts
}
set {
for path in paths {
self[method, path] = newValue
}
}
}
}
/// This wraps up the routing related functionality.
/// Enable the routing system by calling:
/// ```
/// Routing.Handler.registerGlobally()
/// ```
/// This should be done in your `PerfectServerModuleInit` function.
/// The system supports HTTP method based routing, wildcards and variables.
///
/// Add routes in the following manner:
/// ```
/// Routing.Routes["GET", ["/", "index.html"] ] = { (_:WebResponse) in return IndexHandler() }
/// Routing.Routes["/foo/*/baz"] = { _ in return EchoHandler() }
/// Routing.Routes["/foo/bar/baz"] = { _ in return EchoHandler() }
/// Routing.Routes["GET", "/user/{id}/baz"] = { _ in return Echo2Handler() }
/// Routing.Routes["POST", "/user/{id}/baz"] = { _ in return Echo3Handler() }
/// ```
/// The closure you provide should return an instance of `PageHandler`. It is provided the WebResponse object to permit further customization.
/// Variables set by the routing process can be accessed through the `WebRequest.urlVariables` dictionary.
/// Note that a PageHandler *MUST* call `WebResponse.requestCompletedCallback()` when the request has completed.
/// This does not need to be done within the `handleRequest` method.
public class Routing {
/// The routes which have been configured.
static public var Routes = RouteMap()
private init() {}
/// This is the main handler for the logic of the URL routing system.
/// If must be enabled by calling `Routing.Handler.registerGlobally`
public class Handler: RequestHandler {
/// Install the URL routing system.
/// This is required if this system is to be utilized, otherwise it will not be available.
static public func registerGlobally() {
PageHandlerRegistry.addRequestHandler { (_:WebResponse) -> RequestHandler in
return Routing.Handler()
}
}
/// Handle the request, triggering the routing system.
/// If a route is discovered the request is sent to the new handler.
public func handleRequest(request: WebRequest, response: WebResponse) {
let pathInfo = request.requestURI().characters.split("?").map { String($0) }.first ?? "/"
if let handler = Routing.Routes[pathInfo, response] {
handler(response).handleRequest(request, response: response)
} else {
response.setStatus(404, message: "NOT FOUND")
response.appendBodyString("The file \(pathInfo) was not found.")
response.requestCompletedCallback()
}
}
}
}
class RouteNode: CustomStringConvertible {
typealias ComponentGenerator = IndexingGenerator<[String]>
var description: String {
return self.descriptionTabbed(0)
}
private func putTabs(count: Int) -> String {
var s = ""
for _ in 0..<count {
s.appendContentsOf("\t")
}
return s
}
func descriptionTabbedInner(tabCount: Int) -> String {
var s = ""
for (_, node) in self.subNodes {
s.appendContentsOf("\(self.putTabs(tabCount))\(node.descriptionTabbed(tabCount+1))")
}
for node in self.variables {
s.appendContentsOf("\(self.putTabs(tabCount))\(node.descriptionTabbed(tabCount+1))")
}
if let node = self.wildCard {
s.appendContentsOf("\(self.putTabs(tabCount))\(node.descriptionTabbed(tabCount+1))")
}
return s
}
func descriptionTabbed(tabCount: Int) -> String {
var s = ""
if let _ = self.handlerGenerator {
s.appendContentsOf("/+h\n")
}
s.appendContentsOf(self.descriptionTabbedInner(tabCount))
return s
}
var handlerGenerator: RequestHandlerGenerator?
var wildCard: RouteNode?
var variables = [RouteNode]()
var subNodes = [String:RouteNode]()
func findHandler(currentComponent: String, generator: ComponentGenerator, webResponse: WebResponse) -> RequestHandlerGenerator? {
var m = generator
if let p = m.next() where p != "/" {
// variables
for node in self.variables {
if let h = node.findHandler(p, generator: m, webResponse: webResponse) {
return self.successfulRoute(currentComponent, handler: node.successfulRoute(p, handler: h, webResponse: webResponse), webResponse: webResponse)
}
}
// paths
if let node = self.subNodes[p] {
if let h = node.findHandler(p, generator: m, webResponse: webResponse) {
return self.successfulRoute(currentComponent, handler: node.successfulRoute(p, handler: h, webResponse: webResponse), webResponse: webResponse)
}
}
// wildcards
if let node = self.wildCard {
if let h = node.findHandler(p, generator: m, webResponse: webResponse) {
return self.successfulRoute(currentComponent, handler: node.successfulRoute(p, handler: h, webResponse: webResponse), webResponse: webResponse)
}
}
} else if self.handlerGenerator != nil {
return self.handlerGenerator
} else {
// wildcards
if let node = self.wildCard {
if let h = node.findHandler("", generator: m, webResponse: webResponse) {
return self.successfulRoute(currentComponent, handler: node.successfulRoute("", handler: h, webResponse: webResponse), webResponse: webResponse)
}
}
}
return nil
}
func successfulRoute(currentComponent: String, handler: RequestHandlerGenerator, webResponse: WebResponse) -> RequestHandlerGenerator {
return handler
}
func addPathSegments(g: ComponentGenerator, h: RequestHandlerGenerator) {
var m = g
if let p = m.next() {
if p == "/" {
self.addPathSegments(m, h: h)
} else {
self.addPathSegment(p, g: m, h: h)
}
} else {
self.handlerGenerator = h
}
}
private func addPathSegment(component: String, g: ComponentGenerator, h: RequestHandlerGenerator) {
if let node = self.nodeForComponent(component) {
node.addPathSegments(g, h: h)
}
}
private func nodeForComponent(component: String) -> RouteNode? {
guard !component.isEmpty else {
return nil
}
if component == "*" {
if self.wildCard == nil {
self.wildCard = RouteWildCard()
}
return self.wildCard
}
if component.characters.count >= 3 && component[component.startIndex] == "{" && component[component.endIndex.predecessor()] == "}" {
let node = RouteVariable(name: component.substringWith(Range(start: component.startIndex.successor(), end: component.endIndex.predecessor())))
self.variables.append(node)
return node
}
if let node = self.subNodes[component] {
return node
}
let node = RoutePath(name: component)
self.subNodes[component] = node
return node
}
}
class RoutePath: RouteNode {
override func descriptionTabbed(tabCount: Int) -> String {
var s = "/\(self.name)"
if let _ = self.handlerGenerator {
s.appendContentsOf("+h\n")
} else {
s.appendContentsOf("\n")
}
s.appendContentsOf(self.descriptionTabbedInner(tabCount))
return s
}
var name = ""
init(name: String) {
self.name = name
}
// RoutePaths don't need to perform any special checking.
// Their path is validated by the fact that they exist in their parent's `subNodes` dict.
}
class RouteWildCard: RouteNode {
override func descriptionTabbed(tabCount: Int) -> String {
var s = "/*"
if let _ = self.handlerGenerator {
s.appendContentsOf("+h\n")
} else {
s.appendContentsOf("\n")
}
s.appendContentsOf(self.descriptionTabbedInner(tabCount))
return s
}
}
class RouteVariable: RouteNode {
override func descriptionTabbed(tabCount: Int) -> String {
var s = "/{\(self.name)}"
if let _ = self.handlerGenerator {
s.appendContentsOf("+h\n")
} else {
s.appendContentsOf("\n")
}
s.appendContentsOf(self.descriptionTabbedInner(tabCount))
return s
}
var name = ""
init(name: String) {
self.name = name
}
override func successfulRoute(currentComponent: String, handler: RequestHandlerGenerator, webResponse: WebResponse) -> RequestHandlerGenerator {
if let decodedComponent = currentComponent.stringByDecodingURL {
webResponse.request.urlVariables[self.name] = decodedComponent
} else {
webResponse.request.urlVariables[self.name] = currentComponent
}
return handler
}
}