HttpRouter.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. //
  2. // HttpRouter.swift
  3. // Swifter
  4. //
  5. // Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
  6. //
  7. import Foundation
  8. open class HttpRouter {
  9. public init() {}
  10. private class Node {
  11. /// The children nodes that form the route
  12. var nodes = [String: Node]()
  13. /// Define whether or not this node is the end of a route
  14. var isEndOfRoute: Bool = false
  15. /// The closure to handle the route
  16. var handler: ((HttpRequest) -> HttpResponse)?
  17. }
  18. private var rootNode = Node()
  19. /// The Queue to handle the thread safe access to the routes
  20. private let queue = DispatchQueue(label: "swifter.httpserverio.httprouter")
  21. public func routes() -> [String] {
  22. var routes = [String]()
  23. for (_, child) in rootNode.nodes {
  24. routes.append(contentsOf: routesForNode(child))
  25. }
  26. return routes
  27. }
  28. private func routesForNode(_ node: Node, prefix: String = "") -> [String] {
  29. var result = [String]()
  30. if node.handler != nil {
  31. result.append(prefix)
  32. }
  33. for (key, child) in node.nodes {
  34. result.append(contentsOf: routesForNode(child, prefix: prefix + "/" + key))
  35. }
  36. return result
  37. }
  38. public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) {
  39. var pathSegments = stripQuery(path).split("/")
  40. if let method = method {
  41. pathSegments.insert(method, at: 0)
  42. } else {
  43. pathSegments.insert("*", at: 0)
  44. }
  45. var pathSegmentsGenerator = pathSegments.makeIterator()
  46. inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
  47. }
  48. public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? {
  49. return queue.sync {
  50. if let method = method {
  51. let pathSegments = (method + "/" + stripQuery(path)).split("/")
  52. var pathSegmentsGenerator = pathSegments.makeIterator()
  53. var params = [String: String]()
  54. if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
  55. return (params, handler)
  56. }
  57. }
  58. let pathSegments = ("*/" + stripQuery(path)).split("/")
  59. var pathSegmentsGenerator = pathSegments.makeIterator()
  60. var params = [String: String]()
  61. if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
  62. return (params, handler)
  63. }
  64. return nil
  65. }
  66. }
  67. private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
  68. var currentNode = node
  69. while let pathSegment = generator.next() {
  70. if let nextNode = currentNode.nodes[pathSegment] {
  71. currentNode = nextNode
  72. } else {
  73. currentNode.nodes[pathSegment] = Node()
  74. currentNode = currentNode.nodes[pathSegment]!
  75. }
  76. }
  77. currentNode.isEndOfRoute = true
  78. return currentNode
  79. }
  80. private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
  81. var matchedRoutes = [Node]()
  82. let pattern = generator.map { $0 }
  83. let numberOfElements = pattern.count
  84. findHandler(&node, params: &params, pattern: pattern, matchedNodes: &matchedRoutes, index: 0, count: numberOfElements)
  85. return matchedRoutes.first?.handler
  86. }
  87. // swiftlint:disable function_parameter_count
  88. /// Find the handlers for a specified route
  89. ///
  90. /// - Parameters:
  91. /// - node: The root node of the tree representing all the routes
  92. /// - params: The parameters of the match
  93. /// - pattern: The pattern or route to find in the routes tree
  94. /// - matchedNodes: An array with the nodes matching the route
  95. /// - index: The index of current position in the generator
  96. /// - count: The number of elements if the route to match
  97. private func findHandler(_ node: inout Node, params: inout [String: String], pattern: [String], matchedNodes: inout [Node], index: Int, count: Int) {
  98. if index < count, let pathToken = pattern[index].removingPercentEncoding {
  99. var currentIndex = index + 1
  100. let variableNodes = node.nodes.filter { $0.0.first == ":" }
  101. if let variableNode = variableNodes.first {
  102. if currentIndex == count && variableNode.1.isEndOfRoute {
  103. // if it's the last element of the pattern and it's a variable, stop the search and
  104. // append a tail as a value for the variable.
  105. let tail = pattern[currentIndex..<count].joined(separator: "/")
  106. if tail.count > 0 {
  107. params[variableNode.0] = pathToken + "/" + tail
  108. } else {
  109. params[variableNode.0] = pathToken
  110. }
  111. matchedNodes.append(variableNode.value)
  112. return
  113. }
  114. params[variableNode.0] = pathToken
  115. findHandler(&node.nodes[variableNode.0]!, params: &params, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  116. }
  117. if var node = node.nodes[pathToken] {
  118. findHandler(&node, params: &params, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  119. }
  120. if var node = node.nodes["*"] {
  121. findHandler(&node, params: &params, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  122. }
  123. if let startStarNode = node.nodes["**"] {
  124. if startStarNode.isEndOfRoute {
  125. // ** at the end of a route works as a catch-all
  126. matchedNodes.append(startStarNode)
  127. return
  128. }
  129. let startStarNodeKeys = startStarNode.nodes.keys
  130. currentIndex += 1
  131. while currentIndex < count, let pathToken = pattern[currentIndex].removingPercentEncoding {
  132. currentIndex += 1
  133. if startStarNodeKeys.contains(pathToken) {
  134. findHandler(&startStarNode.nodes[pathToken]!, params: &params, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  135. }
  136. }
  137. }
  138. }
  139. if node.isEndOfRoute && index == count {
  140. // if it's the last element and the path to match is done then it's a pattern matching
  141. matchedNodes.append(node)
  142. return
  143. }
  144. }
  145. private func stripQuery(_ path: String) -> String {
  146. if let path = path.components(separatedBy: "?").first {
  147. return path
  148. }
  149. return path
  150. }
  151. }
  152. extension String {
  153. func split(_ separator: Character) -> [String] {
  154. return self.split { $0 == separator }.map(String.init)
  155. }
  156. }