-
Notifications
You must be signed in to change notification settings - Fork 554
Expand file tree
/
Copy pathHttpRouter.swift
More file actions
192 lines (156 loc) · 7.04 KB
/
HttpRouter.swift
File metadata and controls
192 lines (156 loc) · 7.04 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
//
// HttpRouter.swift
// Swifter
//
// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
//
import Foundation
open class HttpRouter {
public init() {}
private class Node {
/// The children nodes that form the route
var nodes = [String: Node]()
/// Define whether or not this node is the end of a route
var isEndOfRoute: Bool = false
/// The closure to handle the route
var handler: ((HttpRequest) -> HttpResponse)?
}
private var rootNode = Node()
/// The Queue to handle the thread safe access to the routes
private let queue = DispatchQueue(label: "swifter.httpserverio.httprouter")
public func routes() -> [String] {
var routes = [String]()
for (_, child) in rootNode.nodes {
routes.append(contentsOf: routesForNode(child))
}
return routes
}
private func routesForNode(_ node: Node, prefix: String = "") -> [String] {
var result = [String]()
if node.handler != nil {
result.append(prefix)
}
for (key, child) in node.nodes {
result.append(contentsOf: routesForNode(child, prefix: prefix + "/" + key))
}
return result
}
public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) {
var pathSegments = stripQuery(path).split("/")
if let method = method {
pathSegments.insert(method, at: 0)
} else {
pathSegments.insert("*", at: 0)
}
var pathSegmentsGenerator = pathSegments.makeIterator()
inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
}
public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? {
return queue.sync {
if let method = method {
let pathSegments = (method + "/" + stripQuery(path)).split("/")
var pathSegmentsGenerator = pathSegments.makeIterator()
var params = [String: String]()
if let handler = findHandler(&rootNode, params: ¶ms, generator: &pathSegmentsGenerator) {
return (params, handler)
}
}
let pathSegments = ("*/" + stripQuery(path)).split("/")
var pathSegmentsGenerator = pathSegments.makeIterator()
var params = [String: String]()
if let handler = findHandler(&rootNode, params: ¶ms, generator: &pathSegmentsGenerator) {
return (params, handler)
}
return nil
}
}
private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
var currentNode = node
while let pathSegment = generator.next() {
if let nextNode = currentNode.nodes[pathSegment] {
currentNode = nextNode
} else {
currentNode.nodes[pathSegment] = Node()
currentNode = currentNode.nodes[pathSegment]!
}
}
currentNode.isEndOfRoute = true
return currentNode
}
private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
var matchedRoutes = [Node]()
let pattern = generator.map { $0 }
let numberOfElements = pattern.count
findHandler(&node, params: ¶ms, pattern: pattern, matchedNodes: &matchedRoutes, index: 0, count: numberOfElements)
return matchedRoutes.first?.handler
}
// swiftlint:disable function_parameter_count
/// Find the handlers for a specified route
///
/// - Parameters:
/// - node: The root node of the tree representing all the routes
/// - params: The parameters of the match
/// - pattern: The pattern or route to find in the routes tree
/// - matchedNodes: An array with the nodes matching the route
/// - index: The index of current position in the generator
/// - count: The number of elements if the route to match
private func findHandler(_ node: inout Node, params: inout [String: String], pattern: [String], matchedNodes: inout [Node], index: Int, count: Int) {
if index < count, let pathToken = pattern[index].removingPercentEncoding {
var currentIndex = index + 1
let variableNodes = node.nodes.filter { $0.0.first == ":" }
if let variableNode = variableNodes.first {
if currentIndex == count && variableNode.1.isEndOfRoute {
// if it's the last element of the pattern and it's a variable, stop the search and
// append a tail as a value for the variable.
let tail = pattern[currentIndex..<count].joined(separator: "/")
if tail.count > 0 {
params[variableNode.0] = pathToken + "/" + tail
} else {
params[variableNode.0] = pathToken
}
matchedNodes.append(variableNode.value)
return
}
params[variableNode.0] = pathToken
findHandler(&node.nodes[variableNode.0]!, params: ¶ms, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
}
if var node = node.nodes[pathToken] {
findHandler(&node, params: ¶ms, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
}
if var node = node.nodes["*"] {
findHandler(&node, params: ¶ms, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
}
if let startStarNode = node.nodes["**"] {
if startStarNode.isEndOfRoute {
// ** at the end of a route works as a catch-all
matchedNodes.append(startStarNode)
return
}
let startStarNodeKeys = startStarNode.nodes.keys
currentIndex += 1
while currentIndex < count, let pathToken = pattern[currentIndex].removingPercentEncoding {
currentIndex += 1
if startStarNodeKeys.contains(pathToken) {
findHandler(&startStarNode.nodes[pathToken]!, params: ¶ms, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
}
}
}
}
if node.isEndOfRoute && index == count {
// if it's the last element and the path to match is done then it's a pattern matching
matchedNodes.append(node)
return
}
}
private func stripQuery(_ path: String) -> String {
if let path = path.components(separatedBy: "?").first {
return path
}
return path
}
}
extension String {
func split(_ separator: Character) -> [String] {
return self.split { $0 == separator }.map(String.init)
}
}