Procházet zdrojové kódy

Added support for routing based on HTTP request methods, and fixed login demo

C0deH4cker před 10 roky
rodič
revize
990280f813

+ 1 - 0
Resources/login.html

@@ -4,6 +4,7 @@
 		<link rel="shortcut icon" href="/static/img/favicon.png" />
     	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 		<link href="http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" />
+		<script src="http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
     	<title>Login</title>
   	</head>
 	<body>

+ 29 - 35
Sources/Swifter/DemoServer.swift

@@ -17,7 +17,13 @@ public func demoServer(publicDir: String?) -> HttpServer {
 
     server["/"] = { r in
         var listPage = "Available services:<br><ul>"
-        listPage += server.routes.map({ "<li><a href=\"\($0)\">\($0)</a></li>"}).joinWithSeparator("")
+        for route in server.routes {
+            if route.method == nil || route.method! == .GET {
+                listPage += "<li><a href=\"\(route.path)\">\(route.path)</a></li>"
+            }
+        }
+        
+        listPage += "</ul>"
         return .OK(.Html(listPage))
     }
     
@@ -39,48 +45,36 @@ public func demoServer(publicDir: String?) -> HttpServer {
         return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.url)<h3>Method: \(r.method)</h3><h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
     }
     
-    server["/upload"] = { r in
-        switch r.method.uppercaseString {
-        case "GET":
-            if let rootDir = publicDir {
-                if let html = NSData(contentsOfFile:"\(rootDir)/file.html") {
-                    var array = [UInt8](count: html.length, repeatedValue: 0)
-                    html.getBytes(&array, length: html.length)
-                    return HttpResponse.RAW(200, "OK", nil, array)
-                } else {
-                    return .NotFound
-                }
-            }
-        case "POST":
-            let formFields = r.parseMultiPartFormData()
-            return HttpResponse.OK(.Html(formFields.map({ UInt8ArrayToUTF8String($0.body) }).joinWithSeparator("<br>")))
-        default:
-            return .NotFound
+    server.GET["/upload"] = { r in
+        if let rootDir = publicDir, html = NSData(contentsOfFile:"\(rootDir)/file.html") {
+            var array = [UInt8](count: html.length, repeatedValue: 0)
+            html.getBytes(&array, length: html.length)
+            return HttpResponse.RAW(200, "OK", nil, array)
         }
+        
         return .NotFound
     }
     
-    server["/login"] = { r in
-        switch r.method.uppercaseString {
-        case "GET":
-            if let rootDir = publicDir {
-                if let html = NSData(contentsOfFile:"\(rootDir)/login.html") {
-                    var array = [UInt8](count: html.length, repeatedValue: 0)
-                    html.getBytes(&array, length: html.length)
-                    return HttpResponse.RAW(200, "OK", nil, array)
-                } else {
-                    return .NotFound
-                }
-            }
-        case "POST":
-            let formFields = r.parseUrlencodedForm()
-            return HttpResponse.OK(.Html(formFields.map({ "\($0.0) = \($0.1)" }).joinWithSeparator("<br>")))
-        default:
-            return .NotFound
+    server.POST["/upload"] = { r in
+        let formFields = r.parseMultiPartFormData()
+        return HttpResponse.OK(.Html(formFields.map({ UInt8ArrayToUTF8String($0.body) }).joinWithSeparator("<br>")))
+    }
+    
+    server.GET["/login"] = { r in
+        if let rootDir = publicDir, html = NSData(contentsOfFile:"\(rootDir)/login.html") {
+                var array = [UInt8](count: html.length, repeatedValue: 0)
+                html.getBytes(&array, length: html.length)
+                return HttpResponse.RAW(200, "OK", nil, array)
         }
+        
         return .NotFound
     }
     
+    server.POST["/login"] = { r in
+        let formFields = r.parseUrlencodedForm()
+        return HttpResponse.OK(.Html(formFields.map({ "\($0.0) = \($0.1)" }).joinWithSeparator("<br>")))
+    }
+    
     server["/demo"] = { r in
         return .OK(.Html("<center><h2>Hello Swift</h2><img src=\"https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png\"/><br></center>"))
     }

+ 7 - 1
Sources/Swifter/HttpParser.swift

@@ -14,6 +14,7 @@
 enum HttpParserError: ErrorType {
     case ReadBodyFailed(String)
     case InvalidStatusLine(String)
+    case UnknownRequestMethod(String)
 }
 
 class HttpParser {
@@ -25,7 +26,12 @@ class HttpParser {
         if statusLineTokens.count < 3 {
             throw HttpParserError.InvalidStatusLine(statusLine)
         }
-        let method = statusLineTokens[0]
+        
+        // Make sure the request is of a known type
+        guard let method = HttpRequest.Method(rawValue: statusLineTokens[0]) else {
+            throw HttpParserError.UnknownRequestMethod(statusLine)
+        }
+        
         let path = statusLineTokens[1]
         let urlParams = extractUrlParams(path)
         let headers = try readHeaders(socket)

+ 4 - 1
Sources/Swifter/HttpRequest.swift

@@ -7,10 +7,13 @@
 import Foundation
 
 public struct HttpRequest {
+    public enum Method: String {
+        case GET, POST, PUT, DELETE
+    }
     
     public let url: String
     public let urlParams: [(String, String)]
-    public let method: String
+    public let method: Method
     public let headers: [String: String]
     public let body: [UInt8]?
     public var address: String?

+ 22 - 11
Sources/Swifter/HttpRouter.swift

@@ -7,30 +7,41 @@
 import Foundation
 
 public class HttpRouter {
+    private var handlers: [(method: HttpRequest.Method?, pattern: [String],
+                            handler: HttpServer.Handler)] = []
     
-    private var handlers: [(pattern: [String], handler: HttpServer.Handler)] = []
-    
-    public func routes() -> [String] {
-        return handlers.map { $0.pattern.joinWithSeparator("/") }
+    public func routes() -> [(method: HttpRequest.Method?, path: String)] {
+        return handlers.map { ($0.method, "/" + $0.pattern.joinWithSeparator("/")) }
     }
     
     public func register(path: String, handler: HttpServer.Handler) {
-        handlers.append((path.split("/"), handler))
+        register(nil, path: path, handler: handler)
+    }
+    
+    public func register(method: HttpRequest.Method?, path: String, handler: HttpServer.Handler) {
+        handlers.append((method, path.split("/"), handler))
         handlers.sortInPlace { $0.0.pattern.count < $0.1.pattern.count }
     }
     
     public func unregister(path: String) {
+        unregister(nil, path: path)
+    }
+    
+    public func unregister(method: HttpRequest.Method?, path: String) {
         let p = path.split("/")
-        handlers = handlers.filter { (pattern, handler) -> Bool in
-            return pattern != p
+        handlers = handlers.filter { (meth, pattern, _) -> Bool in
+            return meth != method || pattern != p
         }
     }
     
-    public func select(url: String) -> ([String: String], HttpServer.Handler)? {
+    public func select(method: HttpRequest.Method, url: String)
+                      -> ([String: String], HttpServer.Handler)? {
         let urlTokens = url.split("/")
-        for (pattern, handler) in handlers {
-            if let params = matchParams(pattern, valueTokens: urlTokens) {
-                return (params, handler)
+        for (meth, pattern, handler) in handlers {
+            if meth == nil || meth! == method {
+                if let params = matchParams(pattern, valueTokens: urlTokens) {
+                    return (params, handler)
+                }
             }
         }
         return nil

+ 64 - 3
Sources/Swifter/HttpServer.swift

@@ -12,6 +12,29 @@ import Foundation
 #endif
 
 public class HttpServer {
+    public class MethodRouter {
+        private let method: HttpRequest.Method?
+        private let router: HttpRouter
+        
+        private init(method: HttpRequest.Method?, router: HttpRouter) {
+            self.method = method
+            self.router = router
+        }
+        
+        public subscript(path: String) -> Handler? {
+            set {
+                if let newValue = newValue {
+                    router.register(method, path: path, handler: newValue)
+                }
+                else {
+                    router.unregister(method, path: path)
+                }
+            }
+            get {
+                return nil
+            }
+        }
+    }
     
     static let VERSION = "1.0.2"
     
@@ -23,7 +46,13 @@ public class HttpServer {
     private var clientSockets: Set<Socket> = []
     private let clientSocketsLock = NSLock()
     
-    public init() { }
+    public init() {
+        anyMethod = MethodRouter(method: nil, router: router)
+        getMethod = MethodRouter(method: .GET, router: router)
+        postMethod = MethodRouter(method: .POST, router: router)
+        putMethod = MethodRouter(method: .PUT, router: router)
+        deleteMethod = MethodRouter(method: .DELETE, router: router)
+    }
     
     public subscript(path: String) -> Handler? {
         set {
@@ -39,7 +68,32 @@ public class HttpServer {
         }
     }
     
-    public var routes: [String] {
+    private var anyMethod: MethodRouter
+    public var ANY: MethodRouter {
+        return anyMethod
+    }
+    
+    private var getMethod: MethodRouter
+    public var GET: MethodRouter {
+        return getMethod
+    }
+    
+    private var postMethod: MethodRouter
+    public var POST: MethodRouter {
+        return postMethod
+    }
+    
+    private var putMethod: MethodRouter
+    public var PUT: MethodRouter {
+        return putMethod
+    }
+    
+    private var deleteMethod: MethodRouter
+    public var DELETE: MethodRouter {
+        return deleteMethod
+    }
+    
+    public var routes: [(method: HttpRequest.Method?, path: String)] {
         return router.routes()
     }
     
@@ -51,26 +105,33 @@ public class HttpServer {
                 HttpServer.lock(self.clientSocketsLock) {
                     self.clientSockets.insert(socket)
                 }
+                
                 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
                     let socketAddress = try? socket.peername()
                     let httpParser = HttpParser()
+                    
                     while let request = try? httpParser.readHttpRequest(socket) {
                         let keepAlive = httpParser.supportsKeepAlive(request.headers)
                         var response = HttpResponse.NotFound
-                        if let (params, handler) = self.router.select(request.url) {
+                        
+                        if let (params, handler) = self.router.select(request.method,
+                                                                      url: request.url) {
                             let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, address: socketAddress, params: params)
                             response = handler(updatedRequest)
                         } else {
                             response = HttpResponse.NotFound
                         }
+                        
                         do {
                             try HttpServer.respond(socket, response: response, keepAlive: keepAlive)
                         } catch {
                             print("Failed to send response: \(error)")
                             break
                         }
+                        
                         if !keepAlive { break }
                     }
+                    
                     socket.release()
                     HttpServer.lock(self.clientSocketsLock) {
                         self.clientSockets.remove(socket)