Преглед на файлове

Wildcard (*) can be used as path segment.

Damian Kołakowski преди 10 години
родител
ревизия
8fecb351c5

+ 6 - 6
Sources/DemoServer.swift

@@ -17,18 +17,18 @@ public func demoServer(publicDir: String?) -> HttpServer {
 
 
     server["/"] = { r in
     server["/"] = { r in
         var listPage = "Available services:<br><ul>"
         var listPage = "Available services:<br><ul>"
-        for (method, path) in server.routes {
-            if let m = method {
-                listPage += "<li><a href=\"\(path)\">\(m): \(path)</a></li>"
+        for services in server.routes {
+            if services.isEmpty {
+                listPage += "<li><a href=\"/\">/</a></li>"
             } else {
             } else {
-                listPage += "<li><a href=\"\(path)\">\(path)</a></li>"
+                listPage += "<li><a href=\"\(services)\">\(services)</a></li>"
             }
             }
         }
         }
         listPage += "</ul>"
         listPage += "</ul>"
         return .OK(.Html(listPage))
         return .OK(.Html(listPage))
     }
     }
     
     
-    server["/magic"] = { .OK(.Html("You asked for " + $0.url)) }
+    server["/magic"] = { .OK(.Html("You asked for " + $0.path)) }
     
     
     server["/test/:param1/:param2"] = { r in
     server["/test/:param1/:param2"] = { r in
         var headersInfo = ""
         var headersInfo = ""
@@ -43,7 +43,7 @@ public func demoServer(publicDir: String?) -> HttpServer {
         for token in r.params {
         for token in r.params {
             pathParamsInfo += "\(token.0) : \(token.1)<br>"
             pathParamsInfo += "\(token.0) : \(token.1)<br>"
         }
         }
-        return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.url)<h3>Method:</h3>\(r.method)<h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
+        return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.path)<h3>Method:</h3>\(r.method)<h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
     }
     }
     
     
     server.GET["/upload"] = { r in
     server.GET["/upload"] = { r in

+ 1 - 1
Sources/HttpHandlers.swift

@@ -86,7 +86,7 @@ public class HttpHandlers {
                         do {
                         do {
                             let files = try fileManager.contentsOfDirectoryAtPath(filePath)
                             let files = try fileManager.contentsOfDirectoryAtPath(filePath)
                             var response = "<h3>\(filePath)</h3></br><table>"
                             var response = "<h3>\(filePath)</h3></br><table>"
-                            response += files.map({ "<tr><td><a href=\"\(r.url)/\($0)\">\($0)</a></td></tr>"}).joinWithSeparator("")
+                            response += files.map({ "<tr><td><a href=\"\(r.path)/\($0)\">\($0)</a></td></tr>"}).joinWithSeparator("")
                             response += "</table>"
                             response += "</table>"
                             return HttpResponse.OK(.Html(response))
                             return HttpResponse.OK(.Html(response))
                         } catch {
                         } catch {

+ 3 - 3
Sources/HttpParser.swift

@@ -22,10 +22,10 @@ class HttpParser {
         if statusLineTokens.count < 3 {
         if statusLineTokens.count < 3 {
             throw HttpParserError.InvalidStatusLine(statusLine)
             throw HttpParserError.InvalidStatusLine(statusLine)
         }
         }
-        var request = HttpRequest()
+        let request = HttpRequest()
         request.method = statusLineTokens[0]
         request.method = statusLineTokens[0]
-        request.url = statusLineTokens[1]
-        request.queryParams = extractQueryParams(request.url)
+        request.path = statusLineTokens[1]
+        request.queryParams = extractQueryParams(request.path)
         request.headers = try readHeaders(socket)
         request.headers = try readHeaders(socket)
         if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) {
         if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) {
             request.body = try readBody(socket, size: contentLengthValue)
             request.body = try readBody(socket, size: contentLengthValue)

+ 2 - 2
Sources/HttpRequest.swift

@@ -6,9 +6,9 @@
 
 
 import Foundation
 import Foundation
 
 
-public struct HttpRequest {
+public class HttpRequest {
     
     
-    public var url: String = ""
+    public var path: String = ""
     public var queryParams: [(String, String)] = []
     public var queryParams: [(String, String)] = []
     public var method: String = ""
     public var method: String = ""
     public var headers: [String: String] = [:]
     public var headers: [String: String] = [:]

+ 68 - 41
Sources/HttpRouter.swift

@@ -8,62 +8,89 @@ import Foundation
 
 
 public class HttpRouter {
 public class HttpRouter {
     
     
-    private var handlers: [(String?, pattern: [String], HttpRequest -> HttpResponse)] = []
+    private class Node {
+        var nodes = [String: Node]()
+        var handler: (HttpRequest -> HttpResponse)? = nil
+    }
     
     
-    public func routes() -> [(method: String?, path: String)] {
-        return handlers.map { ($0.0, "/" + $0.pattern.joinWithSeparator("/")) }
+    private var rootNode = Node()
+
+    public func routes() -> [String] {
+        var routes = [String]()
+        for (_, child) in rootNode.nodes {
+            routes.appendContentsOf(routesForNode(child));
+        }
+        return routes
     }
     }
     
     
-    public func register(method: String?, path: String, handler: HttpRequest -> HttpResponse) {
-        handlers.append((method, stripQuery(path).split("/"), handler))
-        handlers.sortInPlace { $0.0.pattern.count < $0.1.pattern.count }
+    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.appendContentsOf(routesForNode(child, prefix: prefix + "/" + key));
+        }
+        return result
     }
     }
     
     
-    public func unregister(method: String?, path: String) {
-        let pathTokens = stripQuery(path).split("/")
-        handlers = handlers.filter { (meth, pattern, _) -> Bool in
-            return meth != method || pattern != pathTokens
+    public func register(method: String?, path: String, handler: (HttpRequest -> HttpResponse)?) {
+        var pathSegments = stripQuery(path).split("/")
+        if let method = method {
+            pathSegments.insert(method, atIndex: 0)
+        } else {
+            pathSegments.insert("*", atIndex: 0)
         }
         }
+        var pathSegmentsGenerator = pathSegments.generate()
+        inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
     }
     }
     
     
-    public func select(method: String?, url: String) -> ([String: String], HttpRequest -> HttpResponse)? {
-        let urlTokens = stripQuery(url).split("/")
-        for (meth, pattern, handler) in handlers {
-            if meth == nil || meth! == method {
-                if let params = matchParams(pattern, valueTokens: urlTokens) {
-                    return (params, handler)
-                }
+    public func route(method: String?, path: String) -> ([String: String], HttpRequest -> HttpResponse)? {
+        if let method = method {
+            let pathSegments = (method + "/" + stripQuery(path)).split("/")
+            var pathSegmentsGenerator = pathSegments.generate()
+            var params = [String:String]()
+            if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
+                return (params, handler)
             }
             }
         }
         }
+        let pathSegments = ("*/" + stripQuery(path)).split("/")
+        var pathSegmentsGenerator = pathSegments.generate()
+        var params = [String:String]()
+        if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
+            return (params, handler)
+        }
         return nil
         return nil
     }
     }
     
     
-    public func matchParams(patternTokens: [String], valueTokens: [String]) -> [String: String]? {
-        var params = [String: String]()
-        for index in 0..<valueTokens.count {
-            if index >= patternTokens.count {
-                return nil
-            }
-            let patternToken = patternTokens[index]
-            let valueToken = valueTokens[index]
-            if patternToken.isEmpty {
-                if patternToken != valueToken {
-                    return nil
-                }
-            }
-            if patternToken.characters.first == ":" {
-#if os(Linux)
-                params[patternToken.substringFromIndex(1)] = valueToken
-#else
-                params[patternToken.substringFromIndex(patternToken.characters.startIndex.successor())] = valueToken
-#endif
-            } else {
-                if patternToken != valueToken {
-                    return nil
-                }
+    private func inflate(inout node: Node, inout generator: IndexingGenerator<[String]>) -> Node {
+        if let pathToken = generator.next() {
+            if let _ = node.nodes[pathToken] {
+                return inflate(&node.nodes[pathToken]!, generator: &generator)
             }
             }
+            var nextNode = Node()
+            node.nodes[pathToken] = nextNode
+            return inflate(&nextNode, generator: &generator)
         }
         }
-        return params
+        return node
+    }
+    
+    private func findHandler(inout node: Node, inout params: [String: String], inout generator: IndexingGenerator<[String]>) -> (HttpRequest -> HttpResponse)? {
+        guard let pathToken = generator.next() else {
+            return node.handler
+        }
+        let variableNodes = node.nodes.filter { $0.0.characters.first == ":" }
+        if let variableNode = variableNodes.first {
+            params[variableNode.0] = pathToken
+            return findHandler(&node.nodes[variableNode.0]!, params: &params, generator: &generator)
+        }
+        if let _ = node.nodes[pathToken] {
+            return findHandler(&node.nodes[pathToken]!, params: &params, generator: &generator)
+        }
+        if let _ = node.nodes["*"] {
+            return findHandler(&node.nodes["*"]!, params: &params, generator: &generator)
+        }
+        return nil
     }
     }
     
     
     private func stripQuery(path: String) -> String {
     private func stripQuery(path: String) -> String {

+ 21 - 29
Sources/HttpServer.swift

@@ -15,50 +15,42 @@ public class HttpServer: HttpServerIO {
     private let router = HttpRouter()
     private let router = HttpRouter()
     
     
     public override init() {
     public override init() {
-        self.DELETE = Route(method: "DELETE", router: self.router)
-        self.UPDATE = Route(method: "UPDATE", router: self.router)
-        self.HEAD   = Route(method: "HEAD", router: self.router)
-        self.POST   = Route(method: "POST", router: self.router)
-        self.GET    = Route(method: "GET", router: self.router)
-        self.PUT    = Route(method: "PUT", router: self.router)
+        self.DELETE = MethodRoute(method: "DELETE", router: router)
+        self.UPDATE = MethodRoute(method: "UPDATE", router: router)
+        self.HEAD   = MethodRoute(method: "HEAD", router: router)
+        self.POST   = MethodRoute(method: "POST", router: router)
+        self.GET    = MethodRoute(method: "GET", router: router)
+        self.PUT    = MethodRoute(method: "PUT", router: router)
     }
     }
     
     
-    public var DELETE, UPDATE, HEAD, POST, GET, PUT : Route;
-    
-    public var routes: [(method: String?, path: String)] {
-        return router.routes();
-    }
+    public var DELETE, UPDATE, HEAD, POST, GET, PUT : MethodRoute;
     
     
     public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
     public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
         set {
         set {
-            if let handler = newValue {
-                self.router.register(nil, path: path, handler: handler)
-            } else {
-                self.router.unregister(nil, path: path)
-            }
+            router.register(nil, path: path, handler: newValue)
         }
         }
         get { return nil }
         get { return nil }
     }
     }
     
     
-    public struct Route {
+    public var routes: [String] {
+        return router.routes();
+    }
+
+    override public func dispatch(method: String, path: String) -> ([String:String], HttpRequest -> HttpResponse) {
+        if let result = router.route(method, path: path) {
+            return result
+        }
+        return super.dispatch(method, path: path)
+    }
+    
+    public struct MethodRoute {
         public let method: String
         public let method: String
         public let router: HttpRouter
         public let router: HttpRouter
         public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
         public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
             set {
             set {
-                if let handler = newValue {
-                    router.register(method, path: path, handler: handler)
-                } else {
-                    router.unregister(method, path: path)
-                }
+                router.register(method, path: path, handler: newValue)
             }
             }
             get { return nil }
             get { return nil }
         }
         }
     }
     }
-
-    override public func dispatch(method: String, url: String) -> ([String:String], HttpRequest -> HttpResponse) {
-        if let handler = router.select(method, url: url) {
-            return handler
-        }
-        return super.dispatch(method, url: url)
-    }
 }
 }

+ 3 - 3
Sources/HttpServerIO.swift

@@ -40,9 +40,9 @@ public class HttpServerIO {
         let address = try? socket.peername()
         let address = try? socket.peername()
         let parser = HttpParser()
         let parser = HttpParser()
         while let request = try? parser.readHttpRequest(socket) {
         while let request = try? parser.readHttpRequest(socket) {
-            var request = request
+            let request = request
             let keepAlive = parser.supportsKeepAlive(request.headers)
             let keepAlive = parser.supportsKeepAlive(request.headers)
-            let (params, handler) = self.dispatch(request.method, url: request.url)
+            let (params, handler) = self.dispatch(request.method, path: request.path)
             request.address = address
             request.address = address
             request.params = params;
             request.params = params;
             let response = handler(request)
             let response = handler(request)
@@ -57,7 +57,7 @@ public class HttpServerIO {
         socket.release()
         socket.release()
     }
     }
     
     
-    public func dispatch(method: String, url: String) -> ([String: String], HttpRequest -> HttpResponse) {
+    public func dispatch(method: String, path: String) -> ([String: String], HttpRequest -> HttpResponse) {
         return ([:], { _ in HttpResponse.NotFound })
         return ([:], { _ in HttpResponse.NotFound })
     }
     }
     
     

BIN
Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/UserInterfaceState.xcuserstate


+ 112 - 0
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -1390,5 +1390,117 @@
             landmarkType = "5">
             landmarkType = "5">
          </BreakpointContent>
          </BreakpointContent>
       </BreakpointProxy>
       </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "67"
+            endingLineNumber = "67"
+            landmarkName = "inflate(_:generator:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "80"
+            endingLineNumber = "80"
+            landmarkName = "findHandler(_:params:generator:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "71"
+            endingLineNumber = "71"
+            landmarkName = "inflate(_:generator:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "27"
+            endingLineNumber = "27"
+            landmarkName = "routesForNode(_:prefix:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "22"
+            endingLineNumber = "22"
+            landmarkName = "routes()"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/DemoServer.swift"
+            timestampString = "473206811.730477"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "19"
+            endingLineNumber = "19"
+            landmarkName = "demoServer(_:)"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpRouter.swift"
+            timestampString = "473207265.687604"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "34"
+            endingLineNumber = "34"
+            landmarkName = "routesForNode(_:prefix:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
    </Breakpoints>
    </Breakpoints>
 </Bucket>
 </Bucket>