1
0

HttpRouter.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. }
  11. private class Node {
  12. var nodes = [String: Node]()
  13. var handler: ((HttpRequest) -> HttpResponse)? = nil
  14. }
  15. private var rootNode = Node()
  16. public func routes() -> [String] {
  17. var routes = [String]()
  18. for (_, child) in rootNode.nodes {
  19. routes.append(contentsOf: routesForNode(child));
  20. }
  21. return routes
  22. }
  23. private func routesForNode(_ node: Node, prefix: String = "") -> [String] {
  24. var result = [String]()
  25. if let _ = node.handler {
  26. result.append(prefix)
  27. }
  28. for (key, child) in node.nodes {
  29. result.append(contentsOf: routesForNode(child, prefix: prefix + "/" + key));
  30. }
  31. return result
  32. }
  33. public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) {
  34. var pathSegments = stripQuery(path).split("/")
  35. if let method = method {
  36. pathSegments.insert(method, at: 0)
  37. } else {
  38. pathSegments.insert("*", at: 0)
  39. }
  40. var pathSegmentsGenerator = pathSegments.makeIterator()
  41. inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
  42. }
  43. public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? {
  44. if let method = method {
  45. let pathSegments = (method + "/" + stripQuery(path)).split("/")
  46. var pathSegmentsGenerator = pathSegments.makeIterator()
  47. var params = [String:String]()
  48. if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
  49. return (params, handler)
  50. }
  51. }
  52. let pathSegments = ("*/" + stripQuery(path)).split("/")
  53. var pathSegmentsGenerator = pathSegments.makeIterator()
  54. var params = [String:String]()
  55. if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
  56. return (params, handler)
  57. }
  58. return nil
  59. }
  60. private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
  61. if let pathSegment = generator.next() {
  62. if let _ = node.nodes[pathSegment] {
  63. return inflate(&node.nodes[pathSegment]!, generator: &generator)
  64. }
  65. var nextNode = Node()
  66. node.nodes[pathSegment] = nextNode
  67. return inflate(&nextNode, generator: &generator)
  68. }
  69. return node
  70. }
  71. private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
  72. var matchedRoutes = [Node]()
  73. findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedRoutes, index: 0, count: generator.reversed().count)
  74. return matchedRoutes.first?.handler
  75. }
  76. /// Find the handlers for a specified route
  77. ///
  78. /// - Parameters:
  79. /// - node: The root node of the tree representing all the routes
  80. /// - params: The parameters of the match
  81. /// - generator: The IndexingIterator to iterate through the pattern to match
  82. /// - matchedNodes: An array with the nodes matching the route
  83. /// - index: The index of current position in the generator
  84. /// - count: The number of elements if the route to match
  85. private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>, matchedNodes: inout [Node], index: Int, count: Int) {
  86. if let pathToken = generator.next()?.removingPercentEncoding {
  87. var currentIndex = index + 1
  88. let variableNodes = node.nodes.filter { $0.0.first == ":" }
  89. if let variableNode = variableNodes.first {
  90. if variableNode.1.nodes.count == 0 {
  91. // if it's the last element of the pattern and it's a variable, stop the search and
  92. // append a tail as a value for the variable.
  93. let tail = generator.joined(separator: "/")
  94. if tail.count > 0 {
  95. params[variableNode.0] = pathToken + "/" + tail
  96. } else {
  97. params[variableNode.0] = pathToken
  98. }
  99. matchedNodes.append(variableNode.value)
  100. return
  101. }
  102. params[variableNode.0] = pathToken
  103. findHandler(&node.nodes[variableNode.0]!, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  104. }
  105. if var node = node.nodes[pathToken] {
  106. findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  107. }
  108. if var node = node.nodes["*"] {
  109. findHandler(&node, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  110. }
  111. if let startStarNode = node.nodes["**"] {
  112. let startStarNodeKeys = startStarNode.nodes.keys
  113. while let pathToken = generator.next() {
  114. currentIndex += 1
  115. if startStarNodeKeys.contains(pathToken) {
  116. findHandler(&startStarNode.nodes[pathToken]!, params: &params, generator: &generator, matchedNodes: &matchedNodes, index: currentIndex, count: count)
  117. }
  118. }
  119. }
  120. } else if let variableNode = node.nodes.filter({ $0.0.first == ":" }).first {
  121. // if it's the last element of the requested URL, check if there is a pattern with variable tail.
  122. if variableNode.value.nodes.isEmpty {
  123. params[variableNode.0] = ""
  124. matchedNodes.append(variableNode.value)
  125. return
  126. }
  127. }
  128. // if it's the last element and the path to match is done then it's a pattern matching
  129. if node.nodes.isEmpty && index == count {
  130. matchedNodes.append(node)
  131. return
  132. }
  133. }
  134. private func stripQuery(_ path: String) -> String {
  135. if let path = path.components(separatedBy: "?").first {
  136. return path
  137. }
  138. return path
  139. }
  140. }
  141. extension String {
  142. func split(_ separator: Character) -> [String] {
  143. return self.split { $0 == separator }.map(String.init)
  144. }
  145. }