1
0
Эх сурвалжийг харах

Initial version of 2.0 (moved to async loop).

Damian Kołakowski 9 жил өмнө
parent
commit
568f8027f3

+ 0 - 0
Sources/String+BASE64.swift → Sources/Base64.swift


+ 193 - 0
Sources/Demo.swift

@@ -0,0 +1,193 @@
+//
+//  DemoServer.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+
+public func demoServer(_ publicDir: String) throws -> Swifter {
+    
+    print(publicDir)
+    
+    let server = try Swifter()
+    
+//    server["/public/:path"] = shareFilesFromDirectory(publicDir)
+//
+//    server["/files/:path"] = directoryBrowser("/")
+//
+//    server["/"] =
+//        html {
+//            "body" ~ {
+//                "ul" ~ {
+//                    for service in server.routes {
+//                        "li" ~ {
+//                            "a(href=\(service))" ~ service
+//                        }
+//                    }
+//                }
+//            }
+//        }
+//    }
+//    
+//    server["/magic"] = html {
+//        "body" ~ ("You asked for " + $0.path))
+//    }
+//    
+//    server["/test/:param1/:param2"] = { r in
+//
+//            html {
+//                "body" ~ {
+//                    h3 { inner = "Address: \(r.address)" }
+//                    h3 { inner = "Url: \(r.path)" }
+//                    h3 { inner = "Method: \(r.method)" }
+//                    
+//                    h3 { inner = "Query:" }
+//                    
+//                    table(r.queryParams) { param in
+//                        tr {
+//                            td { inner = param.0 }
+//                            td { inner = param.1 }
+//                        }
+//                    }
+//                    
+//                    h3 { inner = "Headers:" }
+//                    
+//                    table(r.headers) { header in
+//                        tr {
+//                            td { inner = header.0 }
+//                            td { inner = header.1 }
+//                        }
+//                    }
+//                    
+//                    h3 { inner = "Route params:" }
+//                    
+//                    table(r.params) { param in
+//                        tr {
+//                            td { inner = param.0 }
+//                            td { inner = param.1 }
+//                        }
+//                    }
+//                }
+//            }
+//        }(r)
+//    }
+//    
+//    server.GET["/upload"] = scopes {
+//        html {
+//            "body" ~ {
+//                "form(method=POST,action=/upload,enctype=multipart/form-data)" ~ {
+//                    
+//                    "input(name=my_file1,type=file)" ~ ""
+//                    "input(name=my_file2,type=file)" ~ ""
+//                    "input(name=my_file3,type=file)" ~ ""
+//
+//                    "button(type=submit)" ~ "Upload"
+//                }
+//            }
+//        }
+//    }
+//    
+//    server.POST["/upload"] = { r in
+//        var response = ""
+//        for multipart in r.parseMultiPartFormData() {
+//            response += "Name: \(multipart.name) File name: \(multipart.fileName) Size: \(multipart.body.count)<br>"
+//        }
+//        return HttpResponse.ok(.html(response))
+//    }
+//    
+//    server.GET["/login"] = scopes {
+//        html {
+//            head {
+//                script { src = "http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js" }
+//                stylesheet { href = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" }
+//            }
+//            body {
+//                h3 { inner = "Sign In" }
+//                
+//                form {
+//                    method = "POST"
+//                    action = "/login"
+//                    
+//                    fieldset {
+//                        input { placeholder = "E-mail"; name = "email"; type = "email"; autofocus = "" }
+//                        input { placeholder = "Password"; name = "password"; type = "password"; autofocus = "" }
+//                        a {
+//                            href = "/login"
+//                            button {
+//                                type = "submit"
+//                                inner = "Login"
+//                            }
+//                        }
+//                    }
+//                    
+//                }
+//                javascript {
+//                    src = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js"
+//                }
+//            }
+//        }
+//    }
+//    
+//    server.POST["/login"] = { r in
+//        let formFields = r.parseUrlencodedForm()
+//        return HttpResponse.ok(.html(formFields.map({ "\($0.0) = \($0.1)" }).joined(separator: "<br>")))
+//    }
+//    
+//    server["/demo"] = scopes {
+//        html {
+//            body {
+//                center {
+//                    h2 { inner = "Hello Swift" }
+//                    img { src = "https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png" }
+//                }
+//            }
+//        }
+//    }
+//    
+//    server["/raw"] = { r in
+//        return HttpResponse.raw(200, "OK", ["XXX-Custom-Header": "value"], { try $0.write([UInt8]("test".utf8)) })
+//    }
+//    
+//    server["/redirect"] = { r in
+//        return .movedPermanently("http://www.google.com")
+//    }
+//
+//    server["/long"] = { r in
+//        var longResponse = ""
+//        for k in 0..<1000 { longResponse += "(\(k)),->" }
+//        return .ok(.html(longResponse))
+//    }
+//    
+//    server["/wildcard/*/test/*/:param"] = { r in
+//        return .ok(.html(r.path))
+//    }
+//    
+//    server["/stream"] = { r in
+//        return HttpResponse.raw(200, "OK", nil, { w in
+//            for i in 0...100 {
+//                try w.write([UInt8]("[chunk \(i)]".utf8))
+//            }
+//        })
+//    }
+//    
+//    server["/websocket-echo"] = websocket({ (session, text) in
+//        session.writeText(text)
+//        }, { (session, binary) in
+//        session.writeBinary(binary)
+//    })
+//    
+//    server.notFoundHandler = { r in
+//        return .movedPermanently("https://github.com/404")
+//    }
+//    
+//    server.middleware.append { r in
+//        print("Middleware: \(r.address) -> \(r.method) -> \(r.path)")
+//        return nil
+//    }
+    
+    return server
+}
+    

+ 0 - 195
Sources/DemoServer.swift

@@ -1,195 +0,0 @@
-//
-//  DemoServer.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-
-public func demoServer(_ publicDir: String) -> HttpServer {
-    
-    print(publicDir)
-    
-    let server = HttpServer()
-    
-    server["/public/:path"] = shareFilesFromDirectory(publicDir)
-
-    server["/files/:path"] = directoryBrowser("/")
-
-    server["/"] = scopes {
-        html {
-            body {
-                ul(server.routes) { service in
-                    li {
-                        a { href = service; inner = service }
-                    }
-                }
-            }
-        }
-    }
-    
-    server["/magic"] = { .ok(.html("You asked for " + $0.path)) }
-    
-    server["/test/:param1/:param2"] = { r in
-        scopes {
-            html {
-                body {
-                    h3 { inner = "Address: \(r.address)" }
-                    h3 { inner = "Url: \(r.path)" }
-                    h3 { inner = "Method: \(r.method)" }
-                    
-                    h3 { inner = "Query:" }
-                    
-                    table(r.queryParams) { param in
-                        tr {
-                            td { inner = param.0 }
-                            td { inner = param.1 }
-                        }
-                    }
-                    
-                    h3 { inner = "Headers:" }
-                    
-                    table(r.headers) { header in
-                        tr {
-                            td { inner = header.0 }
-                            td { inner = header.1 }
-                        }
-                    }
-                    
-                    h3 { inner = "Route params:" }
-                    
-                    table(r.params) { param in
-                        tr {
-                            td { inner = param.0 }
-                            td { inner = param.1 }
-                        }
-                    }
-                }
-            }
-        }(r)
-    }
-    
-    server.GET["/upload"] = scopes {
-        html {
-            body {
-                form {
-                    method = "POST"
-                    action = "/upload"
-                    enctype = "multipart/form-data"
-                    
-                    input { name = "my_file1"; type = "file" }
-                    input { name = "my_file2"; type = "file" }
-                    input { name = "my_file3"; type = "file" }
-                    
-                    button {
-                        type = "submit"
-                        inner = "Upload"
-                    }
-                }
-            }
-        }
-    }
-    
-    server.POST["/upload"] = { r in
-        var response = ""
-        for multipart in r.parseMultiPartFormData() {
-            response += "Name: \(multipart.name) File name: \(multipart.fileName) Size: \(multipart.body.count)<br>"
-        }
-        return HttpResponse.ok(.html(response))
-    }
-    
-    server.GET["/login"] = scopes {
-        html {
-            head {
-                script { src = "http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js" }
-                stylesheet { href = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" }
-            }
-            body {
-                h3 { inner = "Sign In" }
-                
-                form {
-                    method = "POST"
-                    action = "/login"
-                    
-                    fieldset {
-                        input { placeholder = "E-mail"; name = "email"; type = "email"; autofocus = "" }
-                        input { placeholder = "Password"; name = "password"; type = "password"; autofocus = "" }
-                        a {
-                            href = "/login"
-                            button {
-                                type = "submit"
-                                inner = "Login"
-                            }
-                        }
-                    }
-                    
-                }
-                javascript {
-                    src = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js"
-                }
-            }
-        }
-    }
-    
-    server.POST["/login"] = { r in
-        let formFields = r.parseUrlencodedForm()
-        return HttpResponse.ok(.html(formFields.map({ "\($0.0) = \($0.1)" }).joined(separator: "<br>")))
-    }
-    
-    server["/demo"] = scopes {
-        html {
-            body {
-                center {
-                    h2 { inner = "Hello Swift" }
-                    img { src = "https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png" }
-                }
-            }
-        }
-    }
-    
-    server["/raw"] = { r in
-        return HttpResponse.raw(200, "OK", ["XXX-Custom-Header": "value"], { try $0.write([UInt8]("test".utf8)) })
-    }
-    
-    server["/redirect"] = { r in
-        return .movedPermanently("http://www.google.com")
-    }
-
-    server["/long"] = { r in
-        var longResponse = ""
-        for k in 0..<1000 { longResponse += "(\(k)),->" }
-        return .ok(.html(longResponse))
-    }
-    
-    server["/wildcard/*/test/*/:param"] = { r in
-        return .ok(.html(r.path))
-    }
-    
-    server["/stream"] = { r in
-        return HttpResponse.raw(200, "OK", nil, { w in
-            for i in 0...100 {
-                try w.write([UInt8]("[chunk \(i)]".utf8))
-            }
-        })
-    }
-    
-    server["/websocket-echo"] = websocket({ (session, text) in
-        session.writeText(text)
-        }, { (session, binary) in
-        session.writeBinary(binary)
-    })
-    
-    server.notFoundHandler = { r in
-        return .movedPermanently("https://github.com/404")
-    }
-    
-    server.middleware.append { r in
-        print("Middleware: \(r.address) -> \(r.method) -> \(r.path)")
-        return nil
-    }
-    
-    return server
-}
-    

+ 27 - 0
Sources/Error.swift

@@ -0,0 +1,27 @@
+//
+//  Error.swift
+//  Swifter
+//
+//  Copyright © 2017 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public enum AsyncError: Error {
+    case parse(String)
+    case async(String)
+    case socketCreation(String)
+    case setReUseAddr(String)
+    case setNoSigPipeFailed(String)
+    case setNonBlockFailed(String)
+    case setReuseAddrFailed(String)
+    case bindFailed(String)
+    case listenFailed(String)
+    case writeFailed(String)
+    case getPeerNameFailed(String)
+    case convertingPeerNameFailed
+    case getNameInfoFailed(String)
+    case acceptFailed(String)
+    case readFailed(String)
+    case httpError(String)
+}

+ 2 - 2
Sources/Files.swift

@@ -6,7 +6,7 @@
 //
 
 import Foundation
-
+/*
 public func shareFile(_ path: String) -> ((HttpRequest) -> HttpResponse) {
     return { r in
         if let file = try? path.openForReading() {
@@ -85,4 +85,4 @@ public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) {
             return HttpResponse.internalServerError
         }
     }
-}
+}*/

+ 140 - 0
Sources/Html.swift

@@ -0,0 +1,140 @@
+//
+//  Html.swift
+//  Swifter
+//
+//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+private var htmlStackBuffer = [UInt64: [UInt8]]()
+
+public class HtmlResponse: Response {
+    
+    public init(_ status: Int = Status.ok.rawValue, _ closure: ((Void) -> Void)) {
+        
+        super.init(status)
+        
+        self.headers.append(("Content-Type", "text/html"))
+        
+        htmlStackBuffer.removeAll(keepingCapacity: true)
+        
+        closure()
+        
+        if let buffer = htmlStackBuffer[Process.tid] {
+            self.body = Array<UInt8>(buffer)
+        }
+    }
+}
+
+public func html(_ status: Int = Status.ok.rawValue, _ closure: ((Void) -> Void)? = nil) -> HtmlResponse {
+    return HtmlResponse(status) {
+        htmlStackBuffer[Process.tid] = [UInt8]()
+        htmlStackBuffer[Process.tid]?.reserveCapacity(1024)
+        htmlStackBuffer[Process.tid]?.append(contentsOf: "<!DOCTYPE html>".utf8)
+        "html" ~ {
+            if let closureFound = closure {
+                closureFound()
+            }
+        }
+    }
+}
+
+infix operator ~
+
+public func ~ (_ left: String, _ closure: ((Void) -> Void)?) {
+    
+    htmlStackBuffer[Process.tid]?.append(UInt8.lessThan)
+    
+    var tagName = [UInt8]()
+    var tagEnd = false
+    
+    for c in left.utf8 {
+        switch c {
+        case UInt8.openingParenthesis:
+            tagEnd = true
+            htmlStackBuffer[Process.tid]?.append(.space)
+        case UInt8.closingParenthesis:
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+        case UInt8.equal:
+            htmlStackBuffer[Process.tid]?.append(.equal)
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+        case UInt8.comma:
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+            htmlStackBuffer[Process.tid]?.append(.space)
+        default:
+            htmlStackBuffer[Process.tid]?.append(c)
+        }
+        if !tagEnd {
+            tagName.append(c)
+        }
+    }
+    
+    htmlStackBuffer[Process.tid]?.append(UInt8.greaterThan)
+    
+    if let closure = closure {
+        closure()
+    }
+    
+    htmlStackBuffer[Process.tid]?.append(UInt8.lessThan)
+    htmlStackBuffer[Process.tid]?.append(UInt8.slash)
+    htmlStackBuffer[Process.tid]?.append(contentsOf: tagName)
+    htmlStackBuffer[Process.tid]?.append(UInt8.greaterThan)
+}
+
+public func ~ (_ left: String, _ right: String) {
+    
+    htmlStackBuffer[Process.tid]?.append(UInt8.lessThan)
+    
+    var tagName = [UInt8]()
+    var tagEnd = false
+    
+    for c in left.utf8 {
+        switch c {
+        case UInt8.openingParenthesis:
+            tagEnd = true
+            htmlStackBuffer[Process.tid]?.append(.space)
+        case UInt8.closingParenthesis:
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+        case UInt8.equal:
+            htmlStackBuffer[Process.tid]?.append(.equal)
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+        case UInt8.comma:
+            htmlStackBuffer[Process.tid]?.append(.doubleQuotes)
+            htmlStackBuffer[Process.tid]?.append(.space)
+        default:
+            htmlStackBuffer[Process.tid]?.append(c)
+        }
+        if !tagEnd {
+            tagName.append(c)
+        }
+    }
+    
+    htmlStackBuffer[Process.tid]?.append(UInt8.greaterThan)
+    htmlStackBuffer[Process.tid]?.append(contentsOf: right.utf8)
+    htmlStackBuffer[Process.tid]?.append(UInt8.lessThan)
+    htmlStackBuffer[Process.tid]?.append(UInt8.slash)
+    htmlStackBuffer[Process.tid]?.append(contentsOf: tagName)
+    htmlStackBuffer[Process.tid]?.append(UInt8.greaterThan)
+}
+
+public func 🦄(port: Int, closure: @escaping (((Response) -> Void) -> Void)) {
+    do {
+        let server = try Server()
+        while true {
+            try server.serve { (request, responder) in
+                closure(responder)
+            }
+        }
+    } catch {
+        print(error)
+    }
+}
+
+public func 🚀(_ responder: ((Response) -> Void), _ text: String? = nil) {
+    if let text = text {
+        responder(Response([UInt8](text.utf8)))
+    } else {
+        responder(Response(404))
+    }
+}

+ 239 - 0
Sources/Http.swift

@@ -0,0 +1,239 @@
+//
+//  Http.swift
+//  Swifter
+//
+//  Copyright © 2016 kolakowski. All rights reserved.
+//
+
+import Foundation
+
+public class Request {
+    
+    public enum HttpVersion { case http10, http11 }
+    
+    public var httpVersion = HttpVersion.http10
+    
+    public var method = ""
+    
+    public var path = ""
+    
+    public var query = [(String, String)]()
+    
+    public var headers = [(String, String)]()
+    
+    public var body = [UInt8]()
+    
+    public var contentLength = 0
+    
+    public func hasToken(_ token: String, forHeader headerName: String) -> Bool {
+        guard let (_, value) = headers.filter({ $0.0 == headerName }).first else {
+            return false
+        }
+        return value
+            .components(separatedBy: ",")
+            .filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token })
+            .count > 0
+    }
+}
+
+public class Response {
+    
+    public init() { }
+    
+    public init(_ status: Status = Status.ok) {
+        self.status = status.rawValue
+    }
+    
+    public init(_ status: Int = Status.ok.rawValue) {
+        self.status = status
+    }
+    
+    public init(_ body: Array<UInt8>) {
+        self.body.append(contentsOf: body)
+    }
+    
+    public init(_ body: ArraySlice<UInt8>) {
+        self.body.append(contentsOf: body)
+    }
+    
+    public var status = Status.ok.rawValue
+    
+    public var headers = [(String, String)]()
+    
+    public var body = [UInt8]()
+    
+    public var processingSuccesor: IncomingDataProcessor? = nil
+}
+
+public class TextResponse: Response {
+    
+    public init(_ status: Int = Status.ok.rawValue, _ text: String) {
+        super.init(status)
+        self.headers.append(("Content-Type", "text/plain"))
+        self.body = [UInt8](text.utf8)
+    }
+}
+
+public enum Status: Int {
+    case `continue` = 100
+    case switchingProtocols = 101
+    case ok = 200
+    case created = 201
+    case accepted = 202
+    case noContent = 204
+    case resetContent = 205
+    case partialContent = 206
+    case movedPerm = 301
+    case notModified = 304
+    case badRequest = 400
+    case unauthorized = 401
+    case forbidden = 403
+    case notFound = 404
+    case internalServerError = 500
+}
+
+public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
+    
+    private enum State {
+        case waitingForHeaders
+        case waitingForBody
+    }
+    
+    private var state = State.waitingForHeaders
+    
+    private let socket: Int32
+    private var buffer = Array<UInt8>()
+    private var request = Request()
+    private let callback: ((Request) throws -> Void)
+    
+    public init(_ socket: Int32, _ closure: @escaping ((Request) throws -> Void)) {
+        self.socket = socket
+        self.callback = closure
+    }
+    
+    public static func == (lhs: HttpIncomingDataPorcessor, rhs: HttpIncomingDataPorcessor) -> Bool {
+        return lhs.socket == rhs.socket
+    }
+    
+    public var hashValue: Int { return Int(self.socket) }
+    
+    public func process(_ chunk: ArraySlice<UInt8>) throws {
+        
+        switch self.state {
+            
+        case .waitingForHeaders:
+            
+            guard self.buffer.count + chunk.count < 4096 else {
+                throw AsyncError.parse("Headers size exceeds the limit.")
+            }
+            
+            var iterator = chunk.makeIterator()
+            
+            while let byte = iterator.next() {
+                if byte != UInt8.cr {
+                    buffer.append(byte)
+                }
+                if buffer.count >= 2 && buffer[buffer.count-1] == UInt8.lf && buffer[buffer.count-2] == UInt8.lf {
+                    self.buffer.removeLast(2)
+                    self.request = try self.consumeHeader(buffer)
+                    self.buffer.removeAll(keepingCapacity: true)
+                    let left = [UInt8](iterator)
+                    self.state = .waitingForBody
+                    try self.process(left[0..<left.count])
+                    break
+                }
+            }
+            
+        case .waitingForBody:
+            
+            guard self.request.body.count + chunk.count <= request.contentLength else {
+                throw AsyncError.parse("Peer sent more data then required ('Content-Length' = \(request.contentLength).")
+            }
+            
+            request.body.append(contentsOf: chunk)
+            
+            if request.body.count == request.contentLength {
+                self.state = .waitingForHeaders
+                try self.callback(request)
+            }
+        }
+    }
+    
+    private func consumeHeader(_ data: [UInt8]) throws -> Request {
+        
+        let lines = data.split(separator: UInt8.lf)
+        
+        guard let requestLine = lines.first else {
+            throw AsyncError.httpError("No status line.")
+        }
+        
+        let requestLineTokens = requestLine.split(separator: UInt8.space)
+        
+        guard requestLineTokens.count >= 3 else {
+            throw AsyncError.httpError("Invalid status line.")
+        }
+        
+        let request = Request()
+        
+        if requestLineTokens[2] == [0x48, 0x54,  0x54,  0x50, 0x2f, 0x31, 0x2e, 0x30] {
+            request.httpVersion = .http10
+        } else if requestLineTokens[2] == [0x48, 0x54,  0x54,  0x50, 0x2f, 0x31, 0x2e, 0x31] {
+            request.httpVersion = .http11
+        } else {
+            throw AsyncError.parse("Invalid http version: \(requestLineTokens[2])")
+        }
+        
+        request.headers = lines
+            .dropFirst()
+            .map { line in
+                let headerTokens = line.split(separator: UInt8.colon, maxSplits: 1)
+                if let name = headerTokens.first, let value = headerTokens.last {
+                    if let nameString = String(bytes: name, encoding: String.Encoding.ascii),
+                        let valueString = String(bytes: value, encoding: String.Encoding.ascii) {
+                        return (nameString.lowercased(), valueString.trimmingCharacters(in: CharacterSet.whitespaces))
+                    }
+                }
+                return ("", "")
+        }
+        
+        if let (_, value) = request.headers
+            .filter({ $0.0 == "content-length" })
+            .first {
+            guard let contentLength = Int(value) else {
+                throw AsyncError.parse("Invalid 'Content-Length' header value \(value).")
+            }
+            request.contentLength = contentLength
+        }
+        
+        guard let method = String(bytes: requestLineTokens[0], encoding: .ascii) else {
+            throw AsyncError.parse("Invalid 'method' value \(requestLineTokens[0]).")
+        }
+        
+        request.method = method
+        
+        guard let path = String(bytes: requestLineTokens[1], encoding: .ascii) else {
+            throw AsyncError.parse("Invalid 'path' value \(requestLineTokens[1]).")
+        }
+        
+        let queryComponents = path.components(separatedBy: "?")
+        
+        if queryComponents.count > 1, let first = queryComponents.first, let last = queryComponents.last {
+            request.path = first
+            request.query = last
+                .components(separatedBy: "&")
+                .reduce([(String, String)]()) { (c, s) -> [(String, String)] in
+                    let tokens = s.components(separatedBy: "=")
+                    if let name = tokens.first, let value = tokens.last {
+                        if let nameDecoded = name.removingPercentEncoding, let valueDecoded = value.removingPercentEncoding {
+                            return c + [(nameDecoded, tokens.count > 1 ? valueDecoded : "")]
+                        }
+                    }
+                    return c
+            }
+        } else {
+            request.path = path
+        }
+        
+        return request
+    }
+}

+ 0 - 74
Sources/HttpParser.swift

@@ -1,74 +0,0 @@
-//
-//  HttpParser.swift
-//  Swifter
-// 
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-enum HttpParserError: Error {
-    case InvalidStatusLine(String)
-}
-
-public class HttpParser {
-    
-    public init() { }
-    
-    public func readHttpRequest(_ socket: Socket) throws -> HttpRequest {
-        let statusLine = try socket.readLine()
-        let statusLineTokens = statusLine.components(separatedBy: " ")
-        if statusLineTokens.count < 3 {
-            throw HttpParserError.InvalidStatusLine(statusLine)
-        }
-        let request = HttpRequest()
-        request.method = statusLineTokens[0]
-        request.path = statusLineTokens[1]
-        request.queryParams = extractQueryParams(request.path)
-        request.headers = try readHeaders(socket)
-        if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) {
-            request.body = try readBody(socket, size: contentLengthValue)
-        }
-        return request
-    }
-    
-    private func extractQueryParams(_ url: String) -> [(String, String)] {
-        let tokens = url.components(separatedBy: "?")
-        guard let query = tokens.last, tokens.count >= 2 else {
-            return []
-        }
-        return query.components(separatedBy: "&").reduce([(String, String)]()) { (c, s) -> [(String, String)] in
-            let tokens = s.components(separatedBy: "=")
-            let name = tokens.first?.removingPercentEncoding
-            let value = tokens.count > 1 ? (tokens.last?.removingPercentEncoding ?? "") : ""
-            if let nameFound = name {
-                return c + [(nameFound, value)]
-            }
-            return c
-        }
-    }
-    
-    private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] {
-        var body = [UInt8]()
-        for _ in 0..<size { body.append(try socket.read()) }
-        return body
-    }
-    
-    private func readHeaders(_ socket: Socket) throws -> [String: String] {
-        var headers = [String: String]()
-        while case let headerLine = try socket.readLine() , !headerLine.isEmpty {
-            let headerTokens = headerLine.components(separatedBy: ":")
-            if let name = headerTokens.first, let value = headerTokens.last {
-                headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces)
-            }
-        }
-        return headers
-    }
-    
-    func supportsKeepAlive(_ headers: [String: String]) -> Bool {
-        if let value = headers["connection"] {
-            return "keep-alive" == value.trimmingCharacters(in: .whitespaces)
-        }
-        return false
-    }
-}

+ 0 - 173
Sources/HttpResponse.swift

@@ -1,173 +0,0 @@
-//
-//  HttpResponse.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-public enum SerializationError: Error {
-    case invalidObject
-    case notSupported
-}
-
-public protocol HttpResponseBodyWriter {
-    func write(_ file: String.File) throws
-    func write(_ data: [UInt8]) throws
-    func write(_ data: ArraySlice<UInt8>) throws
-    func write(_ data: NSData) throws
-    func write(_ data: Data) throws
-}
-
-public enum HttpResponseBody {
-    
-    case json(AnyObject)
-    case html(String)
-    case text(String)
-    case custom(Any, (Any) throws -> String)
-    
-    func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) {
-        do {
-            switch self {
-            case .json(let object):
-                #if os(Linux)
-                    let data = [UInt8]("Not ready for Linux.".utf8)
-                    return (data.count, {
-                        try $0.write(data)
-                    })
-                #else
-                    guard JSONSerialization.isValidJSONObject(object) else {
-                        throw SerializationError.invalidObject
-                    }
-                    let data = try JSONSerialization.data(withJSONObject: object)
-                    return (data.count, {
-                        try $0.write(data)
-                    })
-                #endif
-            case .text(let body):
-                let data = [UInt8](body.utf8)
-                return (data.count, {
-                    try $0.write(data)
-                })
-            case .html(let body):
-                let serialised = "<html><meta charset=\"UTF-8\"><body>\(body)</body></html>"
-                let data = [UInt8](serialised.utf8)
-                return (data.count, {
-                    try $0.write(data)
-                })
-            case .custom(let object, let closure):
-                let serialised = try closure(object)
-                let data = [UInt8](serialised.utf8)
-                return (data.count, {
-                    try $0.write(data)
-                })
-            }
-        } catch {
-            let data = [UInt8]("Serialisation error: \(error)".utf8)
-            return (data.count, {
-                try $0.write(data)
-            })
-        }
-    }
-}
-
-public enum HttpResponse {
-    
-    case switchProtocols([String: String], (Socket) -> Void)
-    case ok(HttpResponseBody), created, accepted
-    case movedPermanently(String)
-    case badRequest(HttpResponseBody?), unauthorized, forbidden, notFound
-    case internalServerError
-    case raw(Int, String, [String:String]?, ((HttpResponseBodyWriter) throws -> Void)? )
-
-    func statusCode() -> Int {
-        switch self {
-        case .switchProtocols(_, _)   : return 101
-        case .ok(_)                   : return 200
-        case .created                 : return 201
-        case .accepted                : return 202
-        case .movedPermanently        : return 301
-        case .badRequest(_)           : return 400
-        case .unauthorized            : return 401
-        case .forbidden               : return 403
-        case .notFound                : return 404
-        case .internalServerError     : return 500
-        case .raw(let code, _ , _, _) : return code
-        }
-    }
-    
-    func reasonPhrase() -> String {
-        switch self {
-        case .switchProtocols(_, _)    : return "Switching Protocols"
-        case .ok(_)                    : return "OK"
-        case .created                  : return "Created"
-        case .accepted                 : return "Accepted"
-        case .movedPermanently         : return "Moved Permanently"
-        case .badRequest(_)            : return "Bad Request"
-        case .unauthorized             : return "Unauthorized"
-        case .forbidden                : return "Forbidden"
-        case .notFound                 : return "Not Found"
-        case .internalServerError      : return "Internal Server Error"
-        case .raw(_, let phrase, _, _) : return phrase
-        }
-    }
-    
-    func headers() -> [String: String] {
-        var headers = ["Server" : "Swifter \(HttpServer.VERSION)"]
-        switch self {
-        case .switchProtocols(let switchHeaders, _):
-            for (key, value) in switchHeaders {
-                headers[key] = value
-            }
-        case .ok(let body):
-            switch body {
-            case .json(_)   : headers["Content-Type"] = "application/json"
-            case .html(_)   : headers["Content-Type"] = "text/html"
-            default:break
-            }
-        case .movedPermanently(let location):
-            headers["Location"] = location
-        case .raw(_, _, let rawHeaders, _):
-            if let rawHeaders = rawHeaders {
-                for (k, v) in rawHeaders {
-                    headers.updateValue(v, forKey: k)
-                }
-            }
-        default:break
-        }
-        return headers
-    }
-    
-    func content() -> (length: Int, write: ((HttpResponseBodyWriter) throws -> Void)?) {
-        switch self {
-        case .ok(let body)             : return body.content()
-        case .badRequest(let body)     : return body?.content() ?? (-1, nil)
-        case .raw(_, _, _, let writer) : return (-1, writer)
-        default                        : return (-1, nil)
-        }
-    }
-    
-    func socketSession() -> ((Socket) -> Void)?  {
-        switch self {
-        case .switchProtocols(_, let handler) : return handler
-        default: return nil
-        }
-    }
-}
-
-/**
-    Makes it possible to compare handler responses with '==', but
-	ignores any associated values. This should generally be what
-	you want. E.g.:
-	
-    let resp = handler(updatedRequest)
-        if resp == .NotFound {
-        print("Client requested not found: \(request.url)")
-    }
-*/
-
-func ==(inLeft: HttpResponse, inRight: HttpResponse) -> Bool {
-    return inLeft.statusCode() == inRight.statusCode()
-}
-

+ 0 - 75
Sources/HttpServer.swift

@@ -1,75 +0,0 @@
-//
-//  HttpServer.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-public class HttpServer: HttpServerIO {
-    
-    public static let VERSION = "1.3.2"
-    
-    private let router = HttpRouter()
-    
-    public override init() {
-        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)
-        
-        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 : MethodRoute
-    public var delete, update, head, post, get, put : MethodRoute
-    
-    public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? {
-        set {
-            router.register(nil, path: path, handler: newValue)
-        }
-        get { return nil }
-    }
-    
-    public var routes: [String] {
-        return router.routes();
-    }
-    
-    public var notFoundHandler: ((HttpRequest) -> HttpResponse)?
-    
-    public var middleware = Array<(HttpRequest) -> HttpResponse?>()
-
-    override public func dispatch(_ request: HttpRequest) -> ([String:String], (HttpRequest) -> HttpResponse) {
-        for layer in middleware {
-            if let response = layer(request) {
-                return ([:], { _ in response })
-            }
-        }
-        if let result = router.route(request.method, path: request.path) {
-            return result
-        }
-        if let notFoundHandler = self.notFoundHandler {
-            return ([:], notFoundHandler)
-        }
-        return super.dispatch(request)
-    }
-    
-    public struct MethodRoute {
-        public let method: String
-        public let router: HttpRouter
-        public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? {
-            set {
-                router.register(method, path: path, handler: newValue)
-            }
-            get { return nil }
-        }
-    }
-}

+ 0 - 197
Sources/HttpServerIO.swift

@@ -1,197 +0,0 @@
-//
-//  HttpServer.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-import Dispatch
-
-public protocol HttpServerIODelegate: class {
-    func socketConnectionReceived(_ socket: Socket)
-}
-
-public class HttpServerIO {
-
-    public weak var delegate : HttpServerIODelegate?
-
-    private var socket = Socket(socketFileDescriptor: -1)
-    private var sockets = Set<Socket>()
-
-    public enum HttpServerIOState: Int32 {
-        case starting
-        case running
-        case stopping
-        case stopped
-    }
-
-    private var stateValue: Int32 = HttpServerIOState.stopped.rawValue
-
-    public private(set) var state: HttpServerIOState {
-        get {
-            return HttpServerIOState(rawValue: stateValue)!
-        }
-        set(state) {
-            #if !os(Linux)
-            OSAtomicCompareAndSwapInt(self.state.rawValue, state.rawValue, &stateValue)
-            #else
-            //TODO - hehe :)
-            self.stateValue = state.rawValue
-            #endif
-        }
-    }
-
-    public var operating: Bool { get { return self.state == .running } }
-
-    /// String representation of the IPv4 address to receive requests from.
-    /// It's only used when the server is started with `forceIPv4` option set to true.
-    /// Otherwise, `listenAddressIPv6` will be used.
-    public var listenAddressIPv4: String?
-
-    /// String representation of the IPv6 address to receive requests from.
-    /// It's only used when the server is started with `forceIPv4` option set to false.
-    /// Otherwise, `listenAddressIPv4` will be used.
-    public var listenAddressIPv6: String?
-
-    private let queue = DispatchQueue(label: "swifter.httpserverio.clientsockets")
-
-    public func port() throws -> Int {
-        return Int(try socket.port())
-    }
-
-    public func isIPv4() throws -> Bool {
-        return try socket.isIPv4()
-    }
-
-    deinit {
-        stop()
-    }
-
-    @available(macOS 10.10, *)
-    public func start(_ port: in_port_t = 8080, forceIPv4: Bool = false, priority: DispatchQoS.QoSClass = DispatchQoS.QoSClass.background) throws {
-        guard !self.operating else { return }
-        stop()
-        self.state = .starting
-        let address = forceIPv4 ? listenAddressIPv4 : listenAddressIPv6
-        self.socket = try Socket.tcpSocketForListen(port, forceIPv4, SOMAXCONN, address)
-        DispatchQueue.global(qos: priority).async { [weak self] in
-            guard let `self` = self else { return }
-            guard self.operating else { return }
-            while let socket = try? self.socket.acceptClientSocket() {
-                DispatchQueue.global(qos: priority).async { [weak self] in
-                    guard let `self` = self else { return }
-                    guard self.operating else { return }
-                    self.queue.async {
-                        self.sockets.insert(socket)
-                    }
-                    self.handleConnection(socket)
-                    self.queue.async {
-                        self.sockets.remove(socket)
-                    }
-                }
-            }
-            self.stop()
-        }
-        self.state = .running
-    }
-
-    public func stop() {
-        guard self.operating else { return }
-        self.state = .stopping
-        // Shutdown connected peers because they can live in 'keep-alive' or 'websocket' loops.
-        for socket in self.sockets {
-            socket.close()
-        }
-        self.queue.sync {
-            self.sockets.removeAll(keepingCapacity: true)
-        }
-        socket.close()
-        self.state = .stopped
-    }
-
-    public func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) {
-        return ([:], { _ in HttpResponse.notFound })
-    }
-
-    private func handleConnection(_ socket: Socket) {
-        let parser = HttpParser()
-        while self.operating, let request = try? parser.readHttpRequest(socket) {
-            let request = request
-            request.address = try? socket.peername()
-            let (params, handler) = self.dispatch(request)
-            request.params = params
-            let response = handler(request)
-            var keepConnection = parser.supportsKeepAlive(request.headers)
-            do {
-                if self.operating {
-                    keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection)
-                }
-            } catch {
-                print("Failed to send response: \(error)")
-                break
-            }
-            if let session = response.socketSession() {
-                delegate?.socketConnectionReceived(socket)
-                session(socket)
-                break
-            }
-            if !keepConnection { break }
-        }
-        socket.close()
-    }
-
-    private struct InnerWriteContext: HttpResponseBodyWriter {
-        
-        let socket: Socket
-
-        func write(_ file: String.File) throws {
-            try socket.writeFile(file)
-        }
-
-        func write(_ data: [UInt8]) throws {
-            try write(ArraySlice(data))
-        }
-
-        func write(_ data: ArraySlice<UInt8>) throws {
-            try socket.writeUInt8(data)
-        }
-
-        func write(_ data: NSData) throws {
-            try socket.writeData(data)
-        }
-
-        func write(_ data: Data) throws {
-            try socket.writeData(data)
-        }
-    }
-
-    private func respond(_ socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
-        guard self.operating else { return false }
-
-        try socket.writeUTF8("HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
-
-        let content = response.content()
-
-        if content.length >= 0 {
-            try socket.writeUTF8("Content-Length: \(content.length)\r\n")
-        }
-
-        if keepAlive && content.length != -1 {
-            try socket.writeUTF8("Connection: keep-alive\r\n")
-        }
-
-        for (name, value) in response.headers() {
-            try socket.writeUTF8("\(name): \(value)\r\n")
-        }
-
-        try socket.writeUTF8("\r\n")
-
-        if let writeClosure = content.write {
-            let context = InnerWriteContext(socket: socket)
-            try writeClosure(context)
-        }
-
-        return keepAlive && content.length != -1;
-    }
-}

+ 193 - 0
Sources/Linux.swift

@@ -0,0 +1,193 @@
+//
+//  Linux.swift
+//  Swifter
+//
+//  Copyright © 2016 kolakowski. All rights reserved.
+//
+
+#if os(Linux)
+    
+import Glibc
+
+public class LinuxAsyncServer: TcpServer {
+    
+    private var backlog = [Int32: Array<(chunk: [UInt8], done: ((Void) -> TcpWriteDoneAction))>]()
+    private var descriptors = [pollfd]()
+    private let server: Int32
+    
+    public required init(_ port: in_port_t) throws {
+        
+        self.server = try LinuxAsyncServer.nonBlockingSocketForListenening(port)
+        
+        self.descriptors.append(pollfd(fd: self.server, events: Int16(POLLIN), revents: 0))
+    }
+    
+    deinit {
+        cleanup()
+    }
+    
+    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws {
+        let result = Glibc.write(socket, data, data.count)
+        if result == -1 {
+            defer { self.finish(socket) }
+            throw AsyncError.writeFailed(Process.error)
+        }
+        if result == data.count {
+            if done() == .terminate {
+                self.finish(socket)
+            }
+            return
+        }
+        let leftData = [UInt8](data[result..<data.count])
+        for i in 0..<descriptors.count {
+            if descriptors[i].fd == socket {
+                self.backlog[socket]?.append((leftData, done))
+                descriptors[i].events = descriptors[i].events | Int16(POLLOUT)
+                return
+            }
+        }
+    }
+    
+    public func wait(_ callback: ((TcpServerEvent) -> Void)) throws {
+        guard poll(&descriptors, UInt(descriptors.count), -1) != -1 else {
+            throw AsyncError.async(Process.error)
+        }
+        for i in 0..<descriptors.count {
+            if descriptors[i].revents == 0 {
+                continue
+            }
+            if descriptors[i].fd == server {
+                while case let client = accept(server, nil, nil), client > 0 {
+                    try LinuxAsyncServer.setSocketNonBlocking(client)
+                    self.backlog[Int32(client)] = []
+                    descriptors.append(pollfd(fd: client, events: Int16(POLLIN), revents: 0))
+                    callback(TcpServerEvent.connect("", Int32(client)))
+                }
+                if errno != EWOULDBLOCK { throw AsyncError.acceptFailed(Process.error) }
+            } else {
+                if (descriptors[i].revents & Int16(POLLERR) != 0) || (descriptors[i].revents & Int16(POLLHUP) != 0) || (descriptors[i].revents & Int16(POLLNVAL) != 0) {
+                    self.finish(descriptors[i].fd)
+                    callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                    descriptors[i].fd = -1
+                    continue
+                }
+                if descriptors[i].revents & Int16(POLLIN) != 0 {
+                    var buffer = [UInt8](repeating: 0, count: 256)
+                    readLoop: while true {
+                        let result = read(descriptors[i].fd, &buffer, 256)
+                        switch result {
+                        case -1:
+                            if errno != EWOULDBLOCK {
+                                callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                                self.finish(descriptors[i].fd)
+                                descriptors[i].fd = -1
+                            }
+                            break readLoop
+                        case 0:
+                            callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                            self.finish(descriptors[i].fd)
+                            descriptors[i].fd = -1
+                            break readLoop
+                        default:
+                            callback(TcpServerEvent.data("", descriptors[i].fd, buffer[0..<result]))
+                        }
+                    }
+                }
+                if descriptors[i].revents & Int16(POLLOUT) != 0 {
+                    while let backlogElement = self.backlog[Int32(descriptors[i].fd)]?.first {
+                        var chunk = backlogElement.chunk
+                        let result = Glibc.write(Int32(descriptors[i].fd), chunk, chunk.count)
+                        if result == -1 {
+                            finish(Int32(descriptors[i].fd))
+                            callback(TcpServerEvent.disconnect("", Int32(descriptors[i].fd)))
+                            descriptors[i].fd = -1
+                            return
+                        }
+                        if result < chunk.count {
+                            let leftData = [UInt8](chunk[result..<chunk.count])
+                            self.backlog[Int32(descriptors[i].fd)]?.remove(at: 0)
+                            self.backlog[Int32(descriptors[i].fd)]?.insert((chunk: leftData, done: backlogElement.done), at: 0)
+                            return
+                        }
+                        self.backlog[Int32(descriptors[i].fd)]?.removeFirst()
+                        if backlogElement.done() == .terminate {
+                            finish(Int32(descriptors[i].fd))
+                            callback(TcpServerEvent.disconnect("", Int32(descriptors[i].fd)))
+                            descriptors[i].fd = -1
+                            return
+                        }
+                    }
+                    descriptors[i].events = descriptors[i].events & (~Int16(POLLOUT))
+                }
+            }
+        }
+        for i in (0..<descriptors.count).reversed() {
+            if descriptors[i].fd == -1 {
+                descriptors.remove(at: i)
+            }
+        }
+    }
+    
+    public func finish(_ socket: Int32) {
+        self.backlog[socket] = []
+        let _ = Glibc.close(socket)
+    }
+    
+    public func cleanup() {
+        for client in self.descriptors {
+            let _ = Glibc.close(client.fd)
+        }
+        let _ = Glibc.close(Int32(server))
+    }
+    
+    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080) throws -> Int32 {
+        
+        let server = Glibc.socket(AF_INET, Int32(SOCK_STREAM.rawValue), 0)
+        
+        guard server != -1 else {
+            throw AsyncError.socketCreation(Process.error)
+        }
+        
+        var value: Int32 = 1
+        if Glibc.setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
+            defer { let _ = Glibc.close(server) }
+            throw AsyncError.setReuseAddrFailed(Process.error)
+        }
+        
+        if Glibc.fcntl(server, F_SETFL, fcntl(server, F_GETFL, 0) | O_NONBLOCK) == -1 {
+            defer { let _ = Glibc.close(server) }
+            throw AsyncError.setNonBlockFailed(Process.error)
+        }
+        
+        var addr = anyAddrForPort(port)
+        
+        if withUnsafePointer(to: &addr, { bind(server, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size)) })  == -1 {
+            defer { let _ = Glibc.close(server) }
+            throw AsyncError.bindFailed(Process.error)
+        }
+        
+        if Glibc.listen(server, SOMAXCONN) == -1 {
+            defer { let _ = Glibc.close(server) }
+            throw AsyncError.listenFailed(Process.error)
+        }
+        
+        return server
+    }
+    
+    public static func anyAddrForPort(_ port: in_port_t) -> sockaddr_in {
+        var addr = sockaddr_in()
+        addr.sin_family = sa_family_t(AF_INET)
+        addr.sin_port = port.bigEndian
+        addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
+        return addr
+    }
+    
+    public static func setSocketNonBlocking(_ socket: Int32) throws {
+        if Glibc.fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK) == -1 {
+            throw AsyncError.setNonBlockFailed(Process.error)
+        }
+    }
+}
+    
+#endif

+ 272 - 0
Sources/MacOS.swift

@@ -0,0 +1,272 @@
+//
+//  MacOS.swift
+//  Swifter
+//
+//  Copyright © 2016 kolakowski. All rights reserved.
+//
+
+#if os(OSX) || os(iOS)
+    
+import Foundation
+
+public class MacOSAsyncTCPServer: TcpServer {
+    
+    private var backlog = Dictionary<Int32, Array<(chunk: [UInt8], done: ((Void) -> TcpWriteDoneAction))>>()
+    private var peers = Set<Int32>()
+    
+    private let kernelQueue: KernelQueue
+    private let server: UInt
+    
+    public required init(_ port: in_port_t, forceIPv4: Bool, bindAddress: String? = nil) throws {
+        
+        self.kernelQueue = try KernelQueue()
+        
+        self.server = UInt(try MacOSAsyncTCPServer.nonBlockingSocketForListenening(port))
+        
+        self.kernelQueue.subscribe(server, .read)
+    }
+    
+    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws {
+        
+        let result = Darwin.write(socket, data, data.count)
+        
+        if result == -1 {
+            defer { self.finish(socket) }
+            throw AsyncError.writeFailed(Process.error)
+        }
+        
+        if result == data.count {
+            if done() == .terminate {
+                self.finish(socket)
+            }
+            return
+        }
+        
+        self.backlog[socket]?.append(([UInt8](data[result..<data.count]), done))
+        self.kernelQueue.resume(UInt(socket), .write)
+    }
+    
+    public func wait(_ callback: ((TcpServerEvent) -> Void)) throws {
+        try self.kernelQueue.wait { signal in
+            switch signal.event {
+            case .read:
+                if signal.ident == self.server {
+                    let client = try MacOSAsyncTCPServer.acceptAndConfigureClientSocket(Int32(signal.ident))
+                    self.peers.insert(client)
+                    self.backlog[Int32(client)] = []
+                    kernelQueue.subscribe(UInt(client), .read)
+                    kernelQueue.subscribe(UInt(client), .write)
+                    kernelQueue.pause(UInt(client), .write)
+                    callback(.connect("", Int32(client)))
+                } else {
+                    var chunk = [UInt8](repeating: 0, count: signal.data)
+                    let result = Darwin.read(Int32(signal.ident), &chunk, signal.data)
+                    if result <= 0 {
+                        finish(Int32(signal.ident))
+                        callback(.disconnect("", Int32(signal.ident)))
+                    } else {
+                        callback(.data("", Int32(signal.ident), chunk[0..<result]))
+                    }
+                }
+            case .write:
+                while let backlogElement = self.backlog[Int32(signal.ident)]?.first {
+                    var chunk = backlogElement.chunk
+                    let result = Darwin.write(Int32(signal.ident), &chunk, min(chunk.count, signal.data))
+                    if result == -1 {
+                        finish(Int32(signal.ident))
+                        callback(.disconnect("", Int32(signal.ident)))
+                        return
+                    }
+                    if result < chunk.count {
+                        let leftData = [UInt8](chunk[result..<chunk.count])
+                        self.backlog[Int32(signal.ident)]?.remove(at: 0)
+                        self.backlog[Int32(signal.ident)]?.insert((chunk: leftData, done: backlogElement.done), at: 0)
+                        return
+                    }
+                    self.backlog[Int32(signal.ident)]?.removeFirst()
+                    if backlogElement.done() == .terminate {
+                        self.finish(Int32(signal.ident))
+                        callback(.disconnect("", Int32(signal.ident)))
+                        return
+                    }
+                }
+                self.kernelQueue.pause(signal.ident, .write)
+            case .error:
+                if signal.ident == self.server {
+                    throw AsyncError.async(Process.error)
+                } else {
+                    self.finish(Int32(signal.ident))
+                    callback(.disconnect("", Int32(signal.ident)))
+                }
+            }
+        }
+    }
+    
+    deinit {
+        closeAllOpenedSockets()
+    }
+    
+    public func finish(_ socket: Int32) {
+        self.backlog[socket] = []
+        self.peers.remove(socket)
+        let _ = Darwin.close(socket)
+    }
+    
+    public func closeAllOpenedSockets() {
+        for client in self.peers {
+            let _ = Darwin.close(client)
+        }
+        self.peers.removeAll(keepingCapacity: true)
+        let _ = Darwin.close(Int32(server))
+    }
+    
+    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080) throws -> Int32 {
+        
+        let server = Darwin.socket(AF_INET, SOCK_STREAM, 0)
+        
+        guard server != -1 else {
+            throw AsyncError.socketCreation(Process.error)
+        }
+        
+        var value: Int32 = 1
+        if Darwin.setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
+            defer { let _ = Darwin.close(server) }
+            throw AsyncError.setReuseAddrFailed(Process.error)
+        }
+        
+        try setSocketNonBlocking(server)
+        try setSocketNoSigPipe(server)
+        
+        var addr = anyAddrForPort(port)
+        
+        if withUnsafePointer(to: &addr, { Darwin.bind(server, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size)) }) == -1 {
+            defer { let _ = Darwin.close(server) }
+            throw AsyncError.bindFailed(Process.error)
+        }
+        
+        if Darwin.listen(server, SOMAXCONN) == -1 {
+            defer { let _ = Darwin.close(server) }
+            throw AsyncError.listenFailed(Process.error)
+        }
+        
+        return server
+    }
+    
+    public static func acceptAndConfigureClientSocket(_ socket: Int32) throws -> Int32 {
+        
+        guard case let client = Darwin.accept(socket, nil, nil), client != -1 else {
+            throw AsyncError.acceptFailed(Process.error)
+        }
+        
+        try self.setSocketNonBlocking(client)
+        try self.setSocketNoSigPipe(client)
+        
+        return client
+    }
+    
+    public static func anyAddrForPort(_ port: in_port_t) -> sockaddr_in {
+        var addr = sockaddr_in()
+        addr.sin_len = __uint8_t(MemoryLayout<sockaddr_in>.size)
+        addr.sin_family = sa_family_t(AF_INET)
+        addr.sin_port = port.bigEndian
+        addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
+        return addr
+    }
+    
+    public static func setSocketNonBlocking(_ socket: Int32) throws {
+        if Darwin.fcntl(socket, F_SETFL, Darwin.fcntl(socket, F_GETFL, 0) | O_NONBLOCK) == -1 {
+            throw AsyncError.setNonBlockFailed(Process.error)
+        }
+    }
+    
+    public static func setSocketNoSigPipe(_ socket: Int32) throws {
+        var value = 1
+        if Darwin.setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
+            throw AsyncError.setNoSigPipeFailed(Process.error)
+        }
+    }
+}
+
+public class KernelQueue {
+    
+    private var events = Array<kevent>(repeating: kevent(), count: 256)
+    private var changes = Array<kevent>()
+    
+    private let queue: Int32
+    
+    public enum Subscription { case read, write }
+    public enum Event { case read, write, error }
+    
+    public init() throws {
+        guard case let queue = kqueue(), queue != -1 else {
+            throw AsyncError.async(Process.error)
+        }
+        self.queue = queue
+    }
+    
+    public func subscribe(_ ident: UInt, _ event: Subscription) {
+        switch event {
+        case .read  : changes.append(self.event(UInt(ident), Int16(EVFILT_READ), UInt16(EV_ADD) | UInt16(EV_ENABLE)))
+        case .write : changes.append(self.event(UInt(ident), Int16(EVFILT_WRITE), UInt16(EV_ADD) | UInt16(EV_ENABLE)))
+        }
+    }
+    
+    public func unsubscribe(_ ident: UInt, _ event: Subscription) {
+        switch event {
+        case .read  : changes.append(self.event(UInt(ident), Int16(EVFILT_READ), UInt16(EV_DELETE)))
+        case .write : changes.append(self.event(UInt(ident), Int16(EVFILT_WRITE), UInt16(EV_DELETE)))
+        }
+    }
+    
+    public func pause(_ ident: UInt, _ event: Subscription) {
+        switch event {
+        case .read  : changes.append(self.event(UInt(ident), Int16(EVFILT_READ), UInt16(EV_DISABLE)))
+        case .write : changes.append(self.event(UInt(ident), Int16(EVFILT_WRITE), UInt16(EV_DISABLE)))
+        }
+    }
+    
+    public func resume(_ ident: UInt, _ event: Subscription) {
+        switch event {
+        case .read  : changes.append(self.event(UInt(ident), Int16(EVFILT_READ), UInt16(EV_ENABLE)))
+        case .write : changes.append(self.event(UInt(ident), Int16(EVFILT_WRITE), UInt16(EV_ENABLE)))
+        }
+    }
+    
+    private func event(_ ident: UInt, _ filter: Int16, _ flags: UInt16) -> kevent {
+        return kevent(ident: ident, filter: filter, flags: flags, fflags: 0, data: 0, udata: nil)
+    }
+    
+    public func wait(_ callback: (_ tuple: (event: Event, ident: UInt, data: Int)) throws -> (Void)) throws {
+        
+        if !changes.isEmpty {
+            if kevent(self.queue, &changes, Int32(changes.count), nil, 0, nil) == -1 {
+                throw AsyncError.async(Process.error)
+            }
+        }
+        
+        self.changes.removeAll(keepingCapacity: true)
+        
+        guard case let count = kevent(self.queue, nil, 0, &events, Int32(events.count), nil), count != -1 else {
+            throw AsyncError.async(Process.error)
+        }
+        
+        for event in events[0..<Int(count)] {
+            
+            if Int32(event.flags) & EV_EOF != 0 || Int32(event.flags) & EV_ERROR != 0 {
+                try callback((.error, event.ident, 0))
+                continue
+            }
+            if Int32(event.filter) == EVFILT_READ {
+                try callback((.read, event.ident, event.data))
+                continue
+            }
+            if Int32(event.filter) == EVFILT_WRITE {
+                try callback((.write, event.ident, event.data))
+                continue
+            }
+        }
+    }
+}
+    
+#endif

+ 47 - 0
Sources/Misc.swift

@@ -0,0 +1,47 @@
+//
+//  Misc.swift
+//  Swifter
+//
+//  Copyright © 2017 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public extension UInt8 {
+    
+    public static var
+        lf: UInt8 = 10,
+        cr: UInt8 = 13,
+        space: UInt8 = 32,
+        colon: UInt8 = 58,
+        ampersand: UInt8 = 38,
+        lessThan: UInt8 = 60,
+        greaterThan: UInt8 = 62,
+        slash: UInt8 = 47,
+        equal: UInt8 = 61,
+        doubleQuotes: UInt8 = 34,
+        openingParenthesis: UInt8 = 40,
+        closingParenthesis: UInt8 = 41,
+        comma: UInt8 = 44
+}
+
+public struct Process {
+    
+    public static var pid: Int {
+        return Int(getpid())
+    }
+    
+    public static var tid: UInt64 {
+        #if os(Linux)
+            return UInt64(pthread_self())
+        #else
+            var tid: __uint64_t = 0
+            pthread_threadid_np(nil, &tid);
+            return UInt64(tid)
+        #endif
+    }
+    
+    public static var error: String {
+        return String(cString: UnsafePointer(strerror(errno)))
+    }
+}

+ 0 - 40
Sources/Process.swift

@@ -1,40 +0,0 @@
-//
-//  Process
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-public class Process {
-    
-    public static var pid: Int {
-        return Int(getpid())
-    }
-    
-    public static var tid: UInt64 {
-        #if os(Linux)
-            return UInt64(pthread_self())
-        #else
-            var tid: __uint64_t = 0
-            pthread_threadid_np(nil, &tid);
-            return UInt64(tid)
-        #endif
-    }
-    
-    private static var signalsWatchers = Array<(Int32) -> Void>()
-    private static var signalsObserved = false
-    
-    public static func watchSignals(_ callback: @escaping (Int32) -> Void) {
-        if !signalsObserved {
-            [SIGTERM, SIGHUP, SIGSTOP, SIGINT].forEach { item in
-                signal(item) {
-                    signum in Process.signalsWatchers.forEach { $0(signum) }
-                }
-            }
-            signalsObserved = true
-        }
-        signalsWatchers.append(callback)
-    }
-}

+ 13 - 15
Sources/HttpRouter.swift → Sources/Router.swift

@@ -7,18 +7,16 @@
 
 import Foundation
 
+public class RouteNode<T> {
+    var nodes = [String: RouteNode<T>]()
+    var handler: T? = nil
+}
 
-open class HttpRouter {
-    
-    public init() {
-    }
+public class Router<T> {
     
-    private class Node {
-        var nodes = [String: Node]()
-        var handler: ((HttpRequest) -> HttpResponse)? = nil
-    }
+    public init() { }
     
-    private var rootNode = Node()
+    private var rootNode = RouteNode<T>()
 
     public func routes() -> [String] {
         var routes = [String]()
@@ -28,7 +26,7 @@ open class HttpRouter {
         return routes
     }
     
-    private func routesForNode(_ node: Node, prefix: String = "") -> [String] {
+    private func routesForNode(_ node: RouteNode<T>, prefix: String = "") -> [String] {
         var result = [String]()
         if let _ = node.handler {
             result.append(prefix)
@@ -39,7 +37,7 @@ open class HttpRouter {
         return result
     }
     
-    public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) {
+    public func attach(_ method: String?, path: String, handler: T?) {
         var pathSegments = stripQuery(path).split("/")
         if let method = method {
             pathSegments.insert(method, at: 0)
@@ -50,7 +48,7 @@ open class HttpRouter {
         inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
     }
     
-    public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? {
+    public func route(_ method: String?, path: String) -> ([String: String], T)? {
         if let method = method {
             let pathSegments = (method + "/" + stripQuery(path)).split("/")
             var pathSegmentsGenerator = pathSegments.makeIterator()
@@ -68,19 +66,19 @@ open class HttpRouter {
         return nil
     }
     
-    private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
+    private func inflate(_ node: inout RouteNode<T>, generator: inout IndexingIterator<[String]>) -> RouteNode<T> {
         if let pathSegment = generator.next() {
             if let _ = node.nodes[pathSegment] {
                 return inflate(&node.nodes[pathSegment]!, generator: &generator)
             }
-            var nextNode = Node()
+            var nextNode = RouteNode<T>()
             node.nodes[pathSegment] = nextNode
             return inflate(&nextNode, generator: &generator)
         }
         return node
     }
     
-    private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
+    private func findHandler(_ node: inout RouteNode<T>, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> T? {
         guard let pathToken = generator.next() else {
             // if it's the last element of the requested URL, check if there is a pattern with variable tail.
             if let variableNode = node.nodes.filter({ $0.0.characters.first == ":" }).first {

+ 0 - 870
Sources/Scopes.swift

@@ -1,870 +0,0 @@
-//
-//  HttpHandlers+Scopes.swift
-//  Swifter
-//
-//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-public func scopes(_ scope: @escaping Closure) -> ((HttpRequest) -> HttpResponse) {
-    return { r in
-        ScopesBuffer[Process.tid] = ""
-        scope()
-        return .raw(200, "OK", ["Content-Type": "text/html"], {
-            try? $0.write([UInt8](("<!DOCTYPE html>"  + (ScopesBuffer[Process.tid] ?? "")).utf8))
-        })
-    }
-}
-
-public typealias Closure = (Void) -> Void
-
-public var idd: String? = nil
-public var dir: String? = nil
-public var rel: String? = nil
-public var rev: String? = nil
-public var alt: String? = nil
-public var forr: String? = nil
-public var src: String? = nil
-public var type: String? = nil
-public var href: String? = nil
-public var text: String? = nil
-public var abbr: String? = nil
-public var size: String? = nil
-public var face: String? = nil
-public var char: String? = nil
-public var cite: String? = nil
-public var span: String? = nil
-public var data: String? = nil
-public var axis: String? = nil
-public var Name: String? = nil
-public var name: String? = nil
-public var code: String? = nil
-public var link: String? = nil
-public var lang: String? = nil
-public var cols: String? = nil
-public var rows: String? = nil
-public var ismap: String? = nil
-public var shape: String? = nil
-public var style: String? = nil
-public var alink: String? = nil
-public var width: String? = nil
-public var rules: String? = nil
-public var align: String? = nil
-public var frame: String? = nil
-public var vlink: String? = nil
-public var deferr: String? = nil
-public var color: String? = nil
-public var media: String? = nil
-public var title: String? = nil
-public var scope: String? = nil
-public var classs: String? = nil
-public var value: String? = nil
-public var clear: String? = nil
-public var start: String? = nil
-public var label: String? = nil
-public var action: String? = nil
-public var height: String? = nil
-public var method: String? = nil
-public var acceptt: String? = nil
-public var object: String? = nil
-public var scheme: String? = nil
-public var coords: String? = nil
-public var usemap: String? = nil
-public var onblur: String? = nil
-public var nohref: String? = nil
-public var nowrap: String? = nil
-public var hspace: String? = nil
-public var border: String? = nil
-public var valign: String? = nil
-public var vspace: String? = nil
-public var onload: String? = nil
-public var target: String? = nil
-public var prompt: String? = nil
-public var onfocus: String? = nil
-public var enctype: String? = nil
-public var onclick: String? = nil
-public var onkeyup: String? = nil
-public var profile: String? = nil
-public var version: String? = nil
-public var onreset: String? = nil
-public var charset: String? = nil
-public var standby: String? = nil
-public var colspan: String? = nil
-public var charoff: String? = nil
-public var classid: String? = nil
-public var compact: String? = nil
-public var declare: String? = nil
-public var rowspan: String? = nil
-public var checked: String? = nil
-public var archive: String? = nil
-public var bgcolor: String? = nil
-public var content: String? = nil
-public var noshade: String? = nil
-public var summary: String? = nil
-public var headers: String? = nil
-public var onselect: String? = nil
-public var readonly: String? = nil
-public var tabindex: String? = nil
-public var onchange: String? = nil
-public var noresize: String? = nil
-public var disabled: String? = nil
-public var longdesc: String? = nil
-public var codebase: String? = nil
-public var language: String? = nil
-public var datetime: String? = nil
-public var selected: String? = nil
-public var hreflang: String? = nil
-public var onsubmit: String? = nil
-public var multiple: String? = nil
-public var onunload: String? = nil
-public var codetype: String? = nil
-public var scrolling: String? = nil
-public var onkeydown: String? = nil
-public var maxlength: String? = nil
-public var valuetype: String? = nil
-public var accesskey: String? = nil
-public var onmouseup: String? = nil
-public var autofocus: String? = nil
-public var onkeypress: String? = nil
-public var ondblclick: String? = nil
-public var onmouseout: String? = nil
-public var httpEquiv: String? = nil
-public var background: String? = nil
-public var onmousemove: String? = nil
-public var onmouseover: String? = nil
-public var cellpadding: String? = nil
-public var onmousedown: String? = nil
-public var frameborder: String? = nil
-public var marginwidth: String? = nil
-public var cellspacing: String? = nil
-public var placeholder: String? = nil
-public var marginheight: String? = nil
-public var acceptCharset: String? = nil
-
-public var inner: String? = nil
-
-public func a(_ c: Closure) { element("a", c) }
-public func b(_ c: Closure) { element("b", c) }
-public func i(_ c: Closure) { element("i", c) }
-public func p(_ c: Closure) { element("p", c) }
-public func q(_ c: Closure) { element("q", c) }
-public func s(_ c: Closure) { element("s", c) }
-public func u(_ c: Closure) { element("u", c) }
-
-public func br(_ c: Closure) { element("br", c) }
-public func dd(_ c: Closure) { element("dd", c) }
-public func dl(_ c: Closure) { element("dl", c) }
-public func dt(_ c: Closure) { element("dt", c) }
-public func em(_ c: Closure) { element("em", c) }
-public func hr(_ c: Closure) { element("hr", c) }
-public func li(_ c: Closure) { element("li", c) }
-public func ol(_ c: Closure) { element("ol", c) }
-public func rp(_ c: Closure) { element("rp", c) }
-public func rt(_ c: Closure) { element("rt", c) }
-public func td(_ c: Closure) { element("td", c) }
-public func th(_ c: Closure) { element("th", c) }
-public func tr(_ c: Closure) { element("tr", c) }
-public func tt(_ c: Closure) { element("tt", c) }
-public func ul(_ c: Closure) { element("ul", c) }
-
-public func ul<T: Sequence>(_ collection: T, _ c: @escaping (T.Iterator.Element) -> Void) {
-    element("ul", {
-        for item in collection {
-            c(item)
-        }
-    })
-}
-
-public func h1(_ c: Closure) { element("h1", c) }
-public func h2(_ c: Closure) { element("h2", c) }
-public func h3(_ c: Closure) { element("h3", c) }
-public func h4(_ c: Closure) { element("h4", c) }
-public func h5(_ c: Closure) { element("h5", c) }
-public func h6(_ c: Closure) { element("h6", c) }
-
-public func bdi(_ c: Closure) { element("bdi", c) }
-public func bdo(_ c: Closure) { element("bdo", c) }
-public func big(_ c: Closure) { element("big", c) }
-public func col(_ c: Closure) { element("col", c) }
-public func del(_ c: Closure) { element("del", c) }
-public func dfn(_ c: Closure) { element("dfn", c) }
-public func dir(_ c: Closure) { element("dir", c) }
-public func div(_ c: Closure) { element("div", c) }
-public func img(_ c: Closure) { element("img", c) }
-public func ins(_ c: Closure) { element("ins", c) }
-public func kbd(_ c: Closure) { element("kbd", c) }
-public func map(_ c: Closure) { element("map", c) }
-public func nav(_ c: Closure) { element("nav", c) }
-public func pre(_ c: Closure) { element("pre", c) }
-public func rtc(_ c: Closure) { element("rtc", c) }
-public func sub(_ c: Closure) { element("sub", c) }
-public func sup(_ c: Closure) { element("sup", c) }
-
-public func varr(_ c: Closure) { element("var", c) }
-public func wbr(_ c: Closure) { element("wbr", c) }
-public func xmp(_ c: Closure) { element("xmp", c) }
-
-public func abbr(_ c: Closure) { element("abbr", c) }
-public func area(_ c: Closure) { element("area", c) }
-public func base(_ c: Closure) { element("base", c) }
-public func body(_ c: Closure) { element("body", c) }
-public func cite(_ c: Closure) { element("cite", c) }
-public func code(_ c: Closure) { element("code", c) }
-public func data(_ c: Closure) { element("data", c) }
-public func font(_ c: Closure) { element("font", c) }
-public func form(_ c: Closure) { element("form", c) }
-public func head(_ c: Closure) { element("head", c) }
-public func html(_ c: Closure) { element("html", c) }
-public func link(_ c: Closure) { element("link", c) }
-public func main(_ c: Closure) { element("main", c) }
-public func mark(_ c: Closure) { element("mark", c) }
-public func menu(_ c: Closure) { element("menu", c) }
-public func meta(_ c: Closure) { element("meta", c) }
-public func nobr(_ c: Closure) { element("nobr", c) }
-public func ruby(_ c: Closure) { element("ruby", c) }
-public func samp(_ c: Closure) { element("samp", c) }
-public func span(_ c: Closure) { element("span", c) }
-public func time(_ c: Closure) { element("time", c) }
-
-public func aside(_ c: Closure) { element("aside", c) }
-public func audio(_ c: Closure) { element("audio", c) }
-public func blink(_ c: Closure) { element("blink", c) }
-public func embed(_ c: Closure) { element("embed", c) }
-public func frame(_ c: Closure) { element("frame", c) }
-public func image(_ c: Closure) { element("image", c) }
-public func input(_ c: Closure) { element("input", c) }
-public func label(_ c: Closure) { element("label", c) }
-public func meter(_ c: Closure) { element("meter", c) }
-public func param(_ c: Closure) { element("param", c) }
-public func small(_ c: Closure) { element("small", c) }
-public func style(_ c: Closure) { element("style", c) }
-public func table(_ c: Closure) { element("table", c) }
-
-public func table<T: Sequence>(_ collection: T, c: @escaping (T.Iterator.Element) -> Void) {
-    element("table", {
-        for item in collection {
-            c(item)
-        }
-    })
-}
-
-public func tbody(_ c: Closure) { element("tbody", c) }
-
-public func tbody<T: Sequence>(_ collection: T, c: @escaping (T.Iterator.Element) -> Void) {
-    element("tbody", {
-        for item in collection {
-            c(item)
-        }
-    })
-}
-
-public func tfoot(_ c: Closure) { element("tfoot", c) }
-public func thead(_ c: Closure) { element("thead", c) }
-public func title(_ c: Closure) { element("title", c) }
-public func track(_ c: Closure) { element("track", c) }
-public func video(_ c: Closure) { element("video", c) }
-
-public func applet(_ c: Closure) { element("applet", c) }
-public func button(_ c: Closure) { element("button", c) }
-public func canvas(_ c: Closure) { element("canvas", c) }
-public func center(_ c: Closure) { element("center", c) }
-public func dialog(_ c: Closure) { element("dialog", c) }
-public func figure(_ c: Closure) { element("figure", c) }
-public func footer(_ c: Closure) { element("footer", c) }
-public func header(_ c: Closure) { element("header", c) }
-public func hgroup(_ c: Closure) { element("hgroup", c) }
-public func iframe(_ c: Closure) { element("iframe", c) }
-public func keygen(_ c: Closure) { element("keygen", c) }
-public func legend(_ c: Closure) { element("legend", c) }
-public func object(_ c: Closure) { element("object", c) }
-public func option(_ c: Closure) { element("option", c) }
-public func output(_ c: Closure) { element("output", c) }
-public func script(_ c: Closure) { element("script", c) }
-public func select(_ c: Closure) { element("select", c) }
-public func shadow(_ c: Closure) { element("shadow", c) }
-public func source(_ c: Closure) { element("source", c) }
-public func spacer(_ c: Closure) { element("spacer", c) }
-public func strike(_ c: Closure) { element("strike", c) }
-public func strong(_ c: Closure) { element("strong", c) }
-
-public func acronym(_ c: Closure) { element("acronym", c) }
-public func address(_ c: Closure) { element("address", c) }
-public func article(_ c: Closure) { element("article", c) }
-public func bgsound(_ c: Closure) { element("bgsound", c) }
-public func caption(_ c: Closure) { element("caption", c) }
-public func command(_ c: Closure) { element("command", c) }
-public func content(_ c: Closure) { element("content", c) }
-public func details(_ c: Closure) { element("details", c) }
-public func elementt(_ c: Closure) { element("element", c) }
-public func isindex(_ c: Closure) { element("isindex", c) }
-public func listing(_ c: Closure) { element("listing", c) }
-public func marquee(_ c: Closure) { element("marquee", c) }
-public func noembed(_ c: Closure) { element("noembed", c) }
-public func picture(_ c: Closure) { element("picture", c) }
-public func section(_ c: Closure) { element("section", c) }
-public func summary(_ c: Closure) { element("summary", c) }
-
-public func basefont(_ c: Closure) { element("basefont", c) }
-public func colgroup(_ c: Closure) { element("colgroup", c) }
-public func datalist(_ c: Closure) { element("datalist", c) }
-public func fieldset(_ c: Closure) { element("fieldset", c) }
-public func frameset(_ c: Closure) { element("frameset", c) }
-public func menuitem(_ c: Closure) { element("menuitem", c) }
-public func multicol(_ c: Closure) { element("multicol", c) }
-public func noframes(_ c: Closure) { element("noframes", c) }
-public func noscript(_ c: Closure) { element("noscript", c) }
-public func optgroup(_ c: Closure) { element("optgroup", c) }
-public func progress(_ c: Closure) { element("progress", c) }
-public func template(_ c: Closure) { element("template", c) }
-public func textarea(_ c: Closure) { element("textarea", c) }
-
-public func plaintext(_ c: Closure) { element("plaintext", c) }
-public func javascript(_ c: Closure) { element("script", ["type": "text/javascript"], c) }
-public func blockquote(_ c: Closure) { element("blockquote", c) }
-public func figcaption(_ c: Closure) { element("figcaption", c) }
-
-public func stylesheet(_ c: Closure) { element("link", ["rel": "stylesheet", "type": "text/css"], c) }
-
-public func element(_ node: String, _ c: Closure) { evaluate(node, [:], c) }
-public func element(_ node: String, _ attrs: [String: String?] = [:], _ c: Closure) { evaluate(node, attrs, c) }
-
-var ScopesBuffer = [UInt64: String]()
-
-private func evaluate(_ node: String, _ attrs: [String: String?] = [:], _ c: Closure) {
-    
-    // Push the attributes.
-    
-    let stackid = idd
-    let stackdir = dir
-    let stackrel = rel
-    let stackrev = rev
-    let stackalt = alt
-    let stackfor = forr
-    let stacksrc = src
-    let stacktype = type
-    let stackhref = href
-    let stacktext = text
-    let stackabbr = abbr
-    let stacksize = size
-    let stackface = face
-    let stackchar = char
-    let stackcite = cite
-    let stackspan = span
-    let stackdata = data
-    let stackaxis = axis
-    let stackName = Name
-    let stackname = name
-    let stackcode = code
-    let stacklink = link
-    let stacklang = lang
-    let stackcols = cols
-    let stackrows = rows
-    let stackismap = ismap
-    let stackshape = shape
-    let stackstyle = style
-    let stackalink = alink
-    let stackwidth = width
-    let stackrules = rules
-    let stackalign = align
-    let stackframe = frame
-    let stackvlink = vlink
-    let stackdefer = deferr
-    let stackcolor = color
-    let stackmedia = media
-    let stacktitle = title
-    let stackscope = scope
-    let stackclass = classs
-    let stackvalue = value
-    let stackclear = clear
-    let stackstart = start
-    let stacklabel = label
-    let stackaction = action
-    let stackheight = height
-    let stackmethod = method
-    let stackaccept = acceptt
-    let stackobject = object
-    let stackscheme = scheme
-    let stackcoords = coords
-    let stackusemap = usemap
-    let stackonblur = onblur
-    let stacknohref = nohref
-    let stacknowrap = nowrap
-    let stackhspace = hspace
-    let stackborder = border
-    let stackvalign = valign
-    let stackvspace = vspace
-    let stackonload = onload
-    let stacktarget = target
-    let stackprompt = prompt
-    let stackonfocus = onfocus
-    let stackenctype = enctype
-    let stackonclick = onclick
-    let stackonkeyup = onkeyup
-    let stackprofile = profile
-    let stackversion = version
-    let stackonreset = onreset
-    let stackcharset = charset
-    let stackstandby = standby
-    let stackcolspan = colspan
-    let stackcharoff = charoff
-    let stackclassid = classid
-    let stackcompact = compact
-    let stackdeclare = declare
-    let stackrowspan = rowspan
-    let stackchecked = checked
-    let stackarchive = archive
-    let stackbgcolor = bgcolor
-    let stackcontent = content
-    let stacknoshade = noshade
-    let stacksummary = summary
-    let stackheaders = headers
-    let stackonselect = onselect
-    let stackreadonly = readonly
-    let stacktabindex = tabindex
-    let stackonchange = onchange
-    let stacknoresize = noresize
-    let stackdisabled = disabled
-    let stacklongdesc = longdesc
-    let stackcodebase = codebase
-    let stacklanguage = language
-    let stackdatetime = datetime
-    let stackselected = selected
-    let stackhreflang = hreflang
-    let stackonsubmit = onsubmit
-    let stackmultiple = multiple
-    let stackonunload = onunload
-    let stackcodetype = codetype
-    let stackscrolling = scrolling
-    let stackonkeydown = onkeydown
-    let stackmaxlength = maxlength
-    let stackvaluetype = valuetype
-    let stackaccesskey = accesskey
-    let stackonmouseup = onmouseup
-    let stackonkeypress = onkeypress
-    let stackondblclick = ondblclick
-    let stackonmouseout = onmouseout
-    let stackhttpEquiv = httpEquiv
-    let stackbackground = background
-    let stackonmousemove = onmousemove
-    let stackonmouseover = onmouseover
-    let stackcellpadding = cellpadding
-    let stackonmousedown = onmousedown
-    let stackframeborder = frameborder
-    let stackmarginwidth = marginwidth
-    let stackcellspacing = cellspacing
-    let stackplaceholder = placeholder
-    let stackmarginheight = marginheight
-    let stackacceptCharset = acceptCharset
-    let stackinner = inner
-    
-    // Reset the values before a nested scope evalutation.
-    
-    idd = nil
-    dir = nil
-    rel = nil
-    rev = nil
-    alt = nil
-    forr = nil
-    src = nil
-    type = nil
-    href = nil
-    text = nil
-    abbr = nil
-    size = nil
-    face = nil
-    char = nil
-    cite = nil
-    span = nil
-    data = nil
-    axis = nil
-    Name = nil
-    name = nil
-    code = nil
-    link = nil
-    lang = nil
-    cols = nil
-    rows = nil
-    ismap = nil
-    shape = nil
-    style = nil
-    alink = nil
-    width = nil
-    rules = nil
-    align = nil
-    frame = nil
-    vlink = nil
-    deferr = nil
-    color = nil
-    media = nil
-    title = nil
-    scope = nil
-    classs = nil
-    value = nil
-    clear = nil
-    start = nil
-    label = nil
-    action = nil
-    height = nil
-    method = nil
-    acceptt = nil
-    object = nil
-    scheme = nil
-    coords = nil
-    usemap = nil
-    onblur = nil
-    nohref = nil
-    nowrap = nil
-    hspace = nil
-    border = nil
-    valign = nil
-    vspace = nil
-    onload = nil
-    target = nil
-    prompt = nil
-    onfocus = nil
-    enctype = nil
-    onclick = nil
-    onkeyup = nil
-    profile = nil
-    version = nil
-    onreset = nil
-    charset = nil
-    standby = nil
-    colspan = nil
-    charoff = nil
-    classid = nil
-    compact = nil
-    declare = nil
-    rowspan = nil
-    checked = nil
-    archive = nil
-    bgcolor = nil
-    content = nil
-    noshade = nil
-    summary = nil
-    headers = nil
-    onselect = nil
-    readonly = nil
-    tabindex = nil
-    onchange = nil
-    noresize = nil
-    disabled = nil
-    longdesc = nil
-    codebase = nil
-    language = nil
-    datetime = nil
-    selected = nil
-    hreflang = nil
-    onsubmit = nil
-    multiple = nil
-    onunload = nil
-    codetype = nil
-    scrolling = nil
-    onkeydown = nil
-    maxlength = nil
-    valuetype = nil
-    accesskey = nil
-    onmouseup = nil
-    onkeypress = nil
-    ondblclick = nil
-    onmouseout = nil
-    httpEquiv = nil
-    background = nil
-    onmousemove = nil
-    onmouseover = nil
-    cellpadding = nil
-    onmousedown = nil
-    frameborder = nil
-    placeholder = nil
-    marginwidth = nil
-    cellspacing = nil
-    marginheight = nil
-    acceptCharset = nil
-    inner = nil
-    
-    ScopesBuffer[Process.tid] = (ScopesBuffer[Process.tid] ?? "") + "<" + node
-    
-    // Save the current output before the nested scope evalutation.
-    
-    var output = ScopesBuffer[Process.tid] ?? ""
-    
-    // Clear the output buffer for the evalutation.
-    
-    ScopesBuffer[Process.tid] = ""
-    
-    // Evaluate the nested scope.
-    
-    c()
-    
-    // Render attributes set by the evalutation.
-    
-    var mergedAttributes = [String: String?]()
-    
-    if let idd = idd { mergedAttributes["id"] = idd }
-    if let dir = dir { mergedAttributes["dir"] = dir }
-    if let rel = rel { mergedAttributes["rel"] = rel }
-    if let rev = rev { mergedAttributes["rev"] = rev }
-    if let alt = alt { mergedAttributes["alt"] = alt }
-    if let forr = forr { mergedAttributes["for"] = forr }
-    if let src = src { mergedAttributes["src"] = src }
-    if let type = type { mergedAttributes["type"] = type }
-    if let href = href { mergedAttributes["href"] = href }
-    if let text = text { mergedAttributes["text"] = text }
-    if let abbr = abbr { mergedAttributes["abbr"] = abbr }
-    if let size = size { mergedAttributes["size"] = size }
-    if let face = face { mergedAttributes["face"] = face }
-    if let char = char { mergedAttributes["char"] = char }
-    if let cite = cite { mergedAttributes["cite"] = cite }
-    if let span = span { mergedAttributes["span"] = span }
-    if let data = data { mergedAttributes["data"] = data }
-    if let axis = axis { mergedAttributes["axis"] = axis }
-    if let Name = Name { mergedAttributes["Name"] = Name }
-    if let name = name { mergedAttributes["name"] = name }
-    if let code = code { mergedAttributes["code"] = code }
-    if let link = link { mergedAttributes["link"] = link }
-    if let lang = lang { mergedAttributes["lang"] = lang }
-    if let cols = cols { mergedAttributes["cols"] = cols }
-    if let rows = rows { mergedAttributes["rows"] = rows }
-    if let ismap = ismap { mergedAttributes["ismap"] = ismap }
-    if let shape = shape { mergedAttributes["shape"] = shape }
-    if let style = style { mergedAttributes["style"] = style }
-    if let alink = alink { mergedAttributes["alink"] = alink }
-    if let width = width { mergedAttributes["width"] = width }
-    if let rules = rules { mergedAttributes["rules"] = rules }
-    if let align = align { mergedAttributes["align"] = align }
-    if let frame = frame { mergedAttributes["frame"] = frame }
-    if let vlink = vlink { mergedAttributes["vlink"] = vlink }
-    if let deferr = deferr { mergedAttributes["defer"] = deferr }
-    if let color = color { mergedAttributes["color"] = color }
-    if let media = media { mergedAttributes["media"] = media }
-    if let title = title { mergedAttributes["title"] = title }
-    if let scope = scope { mergedAttributes["scope"] = scope }
-    if let classs = classs { mergedAttributes["class"] = classs }
-    if let value = value { mergedAttributes["value"] = value }
-    if let clear = clear { mergedAttributes["clear"] = clear }
-    if let start = start { mergedAttributes["start"] = start }
-    if let label = label { mergedAttributes["label"] = label }
-    if let action = action { mergedAttributes["action"] = action }
-    if let height = height { mergedAttributes["height"] = height }
-    if let method = method { mergedAttributes["method"] = method }
-    if let acceptt = acceptt { mergedAttributes["accept"] = acceptt }
-    if let object = object { mergedAttributes["object"] = object }
-    if let scheme = scheme { mergedAttributes["scheme"] = scheme }
-    if let coords = coords { mergedAttributes["coords"] = coords }
-    if let usemap = usemap { mergedAttributes["usemap"] = usemap }
-    if let onblur = onblur { mergedAttributes["onblur"] = onblur }
-    if let nohref = nohref { mergedAttributes["nohref"] = nohref }
-    if let nowrap = nowrap { mergedAttributes["nowrap"] = nowrap }
-    if let hspace = hspace { mergedAttributes["hspace"] = hspace }
-    if let border = border { mergedAttributes["border"] = border }
-    if let valign = valign { mergedAttributes["valign"] = valign }
-    if let vspace = vspace { mergedAttributes["vspace"] = vspace }
-    if let onload = onload { mergedAttributes["onload"] = onload }
-    if let target = target { mergedAttributes["target"] = target }
-    if let prompt = prompt { mergedAttributes["prompt"] = prompt }
-    if let onfocus = onfocus { mergedAttributes["onfocus"] = onfocus }
-    if let enctype = enctype { mergedAttributes["enctype"] = enctype }
-    if let onclick = onclick { mergedAttributes["onclick"] = onclick }
-    if let onkeyup = onkeyup { mergedAttributes["onkeyup"] = onkeyup }
-    if let profile = profile { mergedAttributes["profile"] = profile }
-    if let version = version { mergedAttributes["version"] = version }
-    if let onreset = onreset { mergedAttributes["onreset"] = onreset }
-    if let charset = charset { mergedAttributes["charset"] = charset }
-    if let standby = standby { mergedAttributes["standby"] = standby }
-    if let colspan = colspan { mergedAttributes["colspan"] = colspan }
-    if let charoff = charoff { mergedAttributes["charoff"] = charoff }
-    if let classid = classid { mergedAttributes["classid"] = classid }
-    if let compact = compact { mergedAttributes["compact"] = compact }
-    if let declare = declare { mergedAttributes["declare"] = declare }
-    if let rowspan = rowspan { mergedAttributes["rowspan"] = rowspan }
-    if let checked = checked { mergedAttributes["checked"] = checked }
-    if let archive = archive { mergedAttributes["archive"] = archive }
-    if let bgcolor = bgcolor { mergedAttributes["bgcolor"] = bgcolor }
-    if let content = content { mergedAttributes["content"] = content }
-    if let noshade = noshade { mergedAttributes["noshade"] = noshade }
-    if let summary = summary { mergedAttributes["summary"] = summary }
-    if let headers = headers { mergedAttributes["headers"] = headers }
-    if let onselect = onselect { mergedAttributes["onselect"] = onselect }
-    if let readonly = readonly { mergedAttributes["readonly"] = readonly }
-    if let tabindex = tabindex { mergedAttributes["tabindex"] = tabindex }
-    if let onchange = onchange { mergedAttributes["onchange"] = onchange }
-    if let noresize = noresize { mergedAttributes["noresize"] = noresize }
-    if let disabled = disabled { mergedAttributes["disabled"] = disabled }
-    if let longdesc = longdesc { mergedAttributes["longdesc"] = longdesc }
-    if let codebase = codebase { mergedAttributes["codebase"] = codebase }
-    if let language = language { mergedAttributes["language"] = language }
-    if let datetime = datetime { mergedAttributes["datetime"] = datetime }
-    if let selected = selected { mergedAttributes["selected"] = selected }
-    if let hreflang = hreflang { mergedAttributes["hreflang"] = hreflang }
-    if let onsubmit = onsubmit { mergedAttributes["onsubmit"] = onsubmit }
-    if let multiple = multiple { mergedAttributes["multiple"] = multiple }
-    if let onunload = onunload { mergedAttributes["onunload"] = onunload }
-    if let codetype = codetype { mergedAttributes["codetype"] = codetype }
-    if let scrolling = scrolling { mergedAttributes["scrolling"] = scrolling }
-    if let onkeydown = onkeydown { mergedAttributes["onkeydown"] = onkeydown }
-    if let maxlength = maxlength { mergedAttributes["maxlength"] = maxlength }
-    if let valuetype = valuetype { mergedAttributes["valuetype"] = valuetype }
-    if let accesskey = accesskey { mergedAttributes["accesskey"] = accesskey }
-    if let onmouseup = onmouseup { mergedAttributes["onmouseup"] = onmouseup }
-    if let onkeypress = onkeypress { mergedAttributes["onkeypress"] = onkeypress }
-    if let ondblclick = ondblclick { mergedAttributes["ondblclick"] = ondblclick }
-    if let onmouseout = onmouseout { mergedAttributes["onmouseout"] = onmouseout }
-    if let httpEquiv = httpEquiv { mergedAttributes["http-equiv"] = httpEquiv }
-    if let background = background { mergedAttributes["background"] = background }
-    if let onmousemove = onmousemove { mergedAttributes["onmousemove"] = onmousemove }
-    if let onmouseover = onmouseover { mergedAttributes["onmouseover"] = onmouseover }
-    if let cellpadding = cellpadding { mergedAttributes["cellpadding"] = cellpadding }
-    if let onmousedown = onmousedown { mergedAttributes["onmousedown"] = onmousedown }
-    if let frameborder = frameborder { mergedAttributes["frameborder"] = frameborder }
-    if let marginwidth = marginwidth { mergedAttributes["marginwidth"] = marginwidth }
-    if let cellspacing = cellspacing { mergedAttributes["cellspacing"] = cellspacing }
-    if let placeholder = placeholder { mergedAttributes["placeholder"] = placeholder }
-    if let marginheight = marginheight { mergedAttributes["marginheight"] = marginheight }
-    if let acceptCharset = acceptCharset { mergedAttributes["accept-charset"] = acceptCharset }
-    
-    for item in attrs.enumerated() {
-        mergedAttributes.updateValue(item.element.1, forKey: item.element.0)
-    }
-    
-    output = output + mergedAttributes.reduce("") {
-        if let value = $0.1.1 {
-            return $0.0 + " \($0.1.0)=\"\(value)\""
-        } else {
-            return $0.0
-        }
-    }
-    
-    if let inner = inner {
-        ScopesBuffer[Process.tid] = output + ">" + (inner) + "</" + node + ">"
-    } else {
-        let current = ScopesBuffer[Process.tid]  ?? ""
-        ScopesBuffer[Process.tid] = output + ">" + current + "</" + node + ">"
-    }
-    
-    // Pop the attributes.
-    
-    idd = stackid
-    dir = stackdir
-    rel = stackrel
-    rev = stackrev
-    alt = stackalt
-    forr = stackfor
-    src = stacksrc
-    type = stacktype
-    href = stackhref
-    text = stacktext
-    abbr = stackabbr
-    size = stacksize
-    face = stackface
-    char = stackchar
-    cite = stackcite
-    span = stackspan
-    data = stackdata
-    axis = stackaxis
-    Name = stackName
-    name = stackname
-    code = stackcode
-    link = stacklink
-    lang = stacklang
-    cols = stackcols
-    rows = stackrows
-    ismap = stackismap
-    shape = stackshape
-    style = stackstyle
-    alink = stackalink
-    width = stackwidth
-    rules = stackrules
-    align = stackalign
-    frame = stackframe
-    vlink = stackvlink
-    deferr = stackdefer
-    color = stackcolor
-    media = stackmedia
-    title = stacktitle
-    scope = stackscope
-    classs = stackclass
-    value = stackvalue
-    clear = stackclear
-    start = stackstart
-    label = stacklabel
-    action = stackaction
-    height = stackheight
-    method = stackmethod
-    acceptt = stackaccept
-    object = stackobject
-    scheme = stackscheme
-    coords = stackcoords
-    usemap = stackusemap
-    onblur = stackonblur
-    nohref = stacknohref
-    nowrap = stacknowrap
-    hspace = stackhspace
-    border = stackborder
-    valign = stackvalign
-    vspace = stackvspace
-    onload = stackonload
-    target = stacktarget
-    prompt = stackprompt
-    onfocus = stackonfocus
-    enctype = stackenctype
-    onclick = stackonclick
-    onkeyup = stackonkeyup
-    profile = stackprofile
-    version = stackversion
-    onreset = stackonreset
-    charset = stackcharset
-    standby = stackstandby
-    colspan = stackcolspan
-    charoff = stackcharoff
-    classid = stackclassid
-    compact = stackcompact
-    declare = stackdeclare
-    rowspan = stackrowspan
-    checked = stackchecked
-    archive = stackarchive
-    bgcolor = stackbgcolor
-    content = stackcontent
-    noshade = stacknoshade
-    summary = stacksummary
-    headers = stackheaders
-    onselect = stackonselect
-    readonly = stackreadonly
-    tabindex = stacktabindex
-    onchange = stackonchange
-    noresize = stacknoresize
-    disabled = stackdisabled
-    longdesc = stacklongdesc
-    codebase = stackcodebase
-    language = stacklanguage
-    datetime = stackdatetime
-    selected = stackselected
-    hreflang = stackhreflang
-    onsubmit = stackonsubmit
-    multiple = stackmultiple
-    onunload = stackonunload
-    codetype = stackcodetype
-    scrolling = stackscrolling
-    onkeydown = stackonkeydown
-    maxlength = stackmaxlength
-    valuetype = stackvaluetype
-    accesskey = stackaccesskey
-    onmouseup = stackonmouseup
-    onkeypress = stackonkeypress
-    ondblclick = stackondblclick
-    onmouseout = stackonmouseout
-    httpEquiv = stackhttpEquiv
-    background = stackbackground
-    onmousemove = stackonmousemove
-    onmouseover = stackonmouseover
-    cellpadding = stackcellpadding
-    onmousedown = stackonmousedown
-    frameborder = stackframeborder
-    placeholder = stackplaceholder
-    marginwidth = stackmarginwidth
-    cellspacing = stackcellspacing
-    marginheight = stackmarginheight
-    acceptCharset = stackacceptCharset
-    
-    inner = stackinner
-}

+ 95 - 0
Sources/Server.swift

@@ -0,0 +1,95 @@
+//
+//  Misc.swift
+//  Swifter
+//
+//  Copyright © 2017 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class Server {
+    
+    private var processors = [Int32 : IncomingDataProcessor]()
+    
+    private let server: TcpServer
+    
+    public init(_ port: in_port_t = 8080, forceIPv4: Bool = false) throws {
+        #if os(Linux)
+            self.server = try LinuxAsyncServer(port)
+        #else
+            self.server = try MacOSAsyncTCPServer(port, forceIPv4: forceIPv4)
+        #endif
+    }
+    
+    public func serve(_ callback: @escaping ((request: Request, responder: @escaping ((Response) -> Void))) -> Void) throws {
+        
+        try self.server.wait { event in
+            
+            switch event {
+                
+            case .connect(_, let socket):
+                
+                self.processors[socket] = HttpIncomingDataPorcessor(socket) { request in
+                    callback((request, { response in
+                        let keepIOSession = self.supportsKeepAlive(request.headers) || request.httpVersion == .http11
+                        var data = [UInt8]()
+                        data.reserveCapacity(1024)
+                        data.append(contentsOf: [UInt8]("HTTP/\(request.httpVersion == .http10 ? "1.0" : "1.1") \(response.status) OK\r\n".utf8))
+                        for (name, value) in response.headers {
+                            data.append(contentsOf: [UInt8]("\(name): \(value)\r\n".utf8))
+                        }
+                        if (keepIOSession) {
+                            data.append(contentsOf: [UInt8]("Connection: keep-alive\r\n".utf8))
+                        }
+                        data.append(contentsOf: [UInt8]("Content-Length: \(response.body.count)\r\n".utf8))
+                        data.append(contentsOf: [13, 10])
+                        data.append(contentsOf: response.body)
+                        do {
+                            try self.server.write(socket, data) {
+                                if let sucessor = response.processingSuccesor {
+                                    self.processors[socket] = sucessor
+                                    return .continue
+                                }
+                                return keepIOSession ? .continue : .terminate
+                            }
+                        } catch {
+                            self.processors.removeValue(forKey: socket)
+                        }
+                    }))
+                }
+                
+            case .disconnect(_, let socket):
+                
+                self.processors.removeValue(forKey: socket)
+                
+            case .data(_, let socket, let chunk):
+                
+                do {
+                    try self.processors[socket]?.process(chunk)
+                } catch {
+                    self.processors.removeValue(forKey: socket)
+                    self.server.finish(socket)
+                }
+            }
+        }
+    }
+    
+    private func supportsKeepAlive(_ headers: Array<(String, String)>) -> Bool {
+        if let (_, value) = headers.filter({ $0.0 == "connection" }).first {
+            return "keep-alive" == value.trimmingCharacters(in: CharacterSet.whitespaces)
+        }
+        return false
+    }
+    
+    private func closeConnection(_ headers: Array<(String, String)>) -> Bool {
+        if let (_, value) = headers.filter({ $0.0 == "connection" }).first {
+            return "close" == value.trimmingCharacters(in: CharacterSet.whitespaces)
+        }
+        return false
+    }
+}
+
+public protocol IncomingDataProcessor {
+    
+    func process(_ chunk: ArraySlice<UInt8>) throws
+}

+ 0 - 47
Sources/Socket+File.swift

@@ -1,47 +0,0 @@
-//
-//  Socket+File.swift
-//  Swifter
-//
-//  Created by Damian Kolakowski on 13/07/16.
-//
-
-import Foundation
-
-#if os(iOS) || os(Linux)
-    
-    struct sf_hdtr { }
-    
-    private func sendfileImpl(_ source: Int32, _ target: Int32, _: off_t, _: UnsafeMutablePointer<off_t>, _: UnsafeMutablePointer<sf_hdtr>, _: Int32) -> Int32 {
-        var buffer = [UInt8](repeating: 0, count: 1024)
-        while true {
-            let readResult = read(source, &buffer, buffer.count)
-            guard readResult > 0 else {
-                return Int32(readResult)
-            }
-            var writeCounter = 0
-            while writeCounter < readResult {
-                let writeResult = write(target, &buffer + writeCounter, readResult - writeCounter)
-                guard writeResult > 0 else {
-                    return Int32(writeResult)
-                }
-                writeCounter = writeCounter + writeResult
-            }
-        }
-    }
-#else
-    private let sendfileImpl = sendfile
-#endif
-
-extension Socket {
-    
-    public func writeFile(_ file: String.File) throws -> Void {
-        var offset: off_t = 0
-        var sf: sf_hdtr = sf_hdtr()
-        let result = sendfileImpl(fileno(file.pointer), self.socketFileDescriptor, 0, &offset, &sf
-            , 0)
-        if result == -1 {
-            throw SocketError.writeFailed("sendfile: " + Errno.description())
-        }
-    }
-    
-}

+ 0 - 115
Sources/Socket+Server.swift

@@ -1,115 +0,0 @@
-//
-//  Socket+Server.swift
-//  Swifter
-//
-//  Created by Damian Kolakowski on 13/07/16.
-//
-
-import Foundation
-
-extension Socket {
-
-    /// - Parameters:
-    ///   - listenAddress: String representation of the address the socket should accept
-    ///       connections from. It should be in IPv4 format if forceIPv4 == true,
-    ///       otherwise - in IPv6.
-    public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN, _ listenAddress: String? = nil) throws -> Socket {
-
-        #if os(Linux)
-            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0)
-        #else
-            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0)
-        #endif
-
-        if socketFileDescriptor == -1 {
-            throw SocketError.socketCreationFailed(Errno.description())
-        }
-
-        var value: Int32 = 1
-        if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
-            let details = Errno.description()
-            Socket.close(socketFileDescriptor)
-            throw SocketError.socketSettingReUseAddrFailed(details)
-        }
-        Socket.setNoSigPipe(socketFileDescriptor)
-
-        var bindResult: Int32 = -1
-        if forceIPv4 {
-            #if os(Linux)
-            var addr = sockaddr_in(
-                sin_family: sa_family_t(AF_INET),
-                sin_port: port.bigEndian,
-                sin_addr: in_addr(s_addr: in_addr_t(0)),
-                sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
-            #else
-            var addr = sockaddr_in(
-                sin_len: UInt8(MemoryLayout<sockaddr_in>.stride),
-                sin_family: UInt8(AF_INET),
-                sin_port: port.bigEndian,
-                sin_addr: in_addr(s_addr: in_addr_t(0)),
-                sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
-            #endif
-            if let address = listenAddress {
-              if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 {
-                // print("\(address) is converted to \(addr.sin_addr).")
-              } else {
-                // print("\(address) is not converted.")
-              }
-            }
-            bindResult = withUnsafePointer(to: &addr) {
-                bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
-            }
-        } else {
-            #if os(Linux)
-            var addr = sockaddr_in6(
-                sin6_family: sa_family_t(AF_INET6),
-                sin6_port: port.bigEndian,
-                sin6_flowinfo: 0,
-                sin6_addr: in6addr_any,
-                sin6_scope_id: 0)
-            #else
-            var addr = sockaddr_in6(
-                sin6_len: UInt8(MemoryLayout<sockaddr_in6>.stride),
-                sin6_family: UInt8(AF_INET6),
-                sin6_port: port.bigEndian,
-                sin6_flowinfo: 0,
-                sin6_addr: in6addr_any,
-                sin6_scope_id: 0)
-            #endif
-            if let address = listenAddress {
-              if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 {
-                //print("\(address) is converted to \(addr.sin6_addr).")
-              } else {
-                //print("\(address) is not converted.")
-              }
-            }
-            bindResult = withUnsafePointer(to: &addr) {
-                bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in6>.size))
-            }
-        }
-
-        if bindResult == -1 {
-            let details = Errno.description()
-            Socket.close(socketFileDescriptor)
-            throw SocketError.bindFailed(details)
-        }
-
-        if listen(socketFileDescriptor, maxPendingConnection) == -1 {
-            let details = Errno.description()
-            Socket.close(socketFileDescriptor)
-            throw SocketError.listenFailed(details)
-        }
-        return Socket(socketFileDescriptor: socketFileDescriptor)
-    }
-    
-    public func acceptClientSocket() throws -> Socket {
-        var addr = sockaddr()
-        var len: socklen_t = 0
-        let clientSocket = accept(self.socketFileDescriptor, &addr, &len)
-        if clientSocket == -1 {
-            throw SocketError.acceptFailed(Errno.description())
-        }
-        Socket.setNoSigPipe(clientSocket)
-        return Socket(socketFileDescriptor: clientSocket)
-    }
-}

+ 0 - 170
Sources/Socket.swift

@@ -1,170 +0,0 @@
-//
-//  Socket.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-
-public enum SocketError: Error {
-    case socketCreationFailed(String)
-    case socketSettingReUseAddrFailed(String)
-    case bindFailed(String)
-    case listenFailed(String)
-    case writeFailed(String)
-    case getPeerNameFailed(String)
-    case convertingPeerNameFailed
-    case getNameInfoFailed(String)
-    case acceptFailed(String)
-    case recvFailed(String)
-    case getSockNameFailed(String)
-}
-
-open class Socket: Hashable, Equatable {
-        
-    let socketFileDescriptor: Int32
-    private var shutdown = false
-
-    
-    public init(socketFileDescriptor: Int32) {
-        self.socketFileDescriptor = socketFileDescriptor
-    }
-    
-    deinit {
-        close()
-    }
-    
-    public var hashValue: Int { return Int(self.socketFileDescriptor) }
-    
-    public func close() {
-        if shutdown {
-            return
-        }
-        shutdown = true
-        Socket.close(self.socketFileDescriptor)
-    }
-    
-    public func port() throws -> in_port_t {
-        var addr = sockaddr_in()
-        return try withUnsafePointer(to: &addr) { pointer in
-            var len = socklen_t(MemoryLayout<sockaddr_in>.size)
-            if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 {
-                throw SocketError.getSockNameFailed(Errno.description())
-            }
-            #if os(Linux)
-                return ntohs(addr.sin_port)
-            #else
-                return Int(OSHostByteOrder()) != OSLittleEndian ? addr.sin_port.littleEndian : addr.sin_port.bigEndian
-            #endif
-        }
-    }
-    
-    public func isIPv4() throws -> Bool {
-        var addr = sockaddr_in()
-        return try withUnsafePointer(to: &addr) { pointer in
-            var len = socklen_t(MemoryLayout<sockaddr_in>.size)
-            if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 {
-                throw SocketError.getSockNameFailed(Errno.description())
-            }
-            return Int32(addr.sin_family) == AF_INET
-        }
-    }
-    
-    public func writeUTF8(_ string: String) throws {
-        try writeUInt8(ArraySlice(string.utf8))
-    }
-    
-    public func writeUInt8(_ data: [UInt8]) throws {
-        try writeUInt8(ArraySlice(data))
-    }
-    
-    public func writeUInt8(_ data: ArraySlice<UInt8>) throws {
-        try data.withUnsafeBufferPointer {
-            try writeBuffer($0.baseAddress!, length: data.count)
-        }
-    }
-
-    public func writeData(_ data: NSData) throws {
-        try writeBuffer(data.bytes, length: data.length)
-    }
-    
-    public func writeData(_ data: Data) throws {
-        try data.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) -> Void in
-            try self.writeBuffer(pointer, length: data.count)
-        }
-    }
-
-    private func writeBuffer(_ pointer: UnsafeRawPointer, length: Int) throws {
-        var sent = 0
-        while sent < length {
-            #if os(Linux)
-                let s = send(self.socketFileDescriptor, pointer + sent, Int(length - sent), Int32(MSG_NOSIGNAL))
-            #else
-                let s = write(self.socketFileDescriptor, pointer + sent, Int(length - sent))
-            #endif
-            if s <= 0 {
-                throw SocketError.writeFailed(Errno.description())
-            }
-            sent += s
-        }
-    }
-    
-    open func read() throws -> UInt8 {
-        var buffer = [UInt8](repeating: 0, count: 1)
-        let next = recv(self.socketFileDescriptor as Int32, &buffer, Int(buffer.count), 0)
-        if next <= 0 {
-            throw SocketError.recvFailed(Errno.description())
-        }
-        return buffer[0]
-    }
-    
-    private static let CR = UInt8(13)
-    private static let NL = UInt8(10)
-    
-    public func readLine() throws -> String {
-        var characters: String = ""
-        var n: UInt8 = 0
-        repeat {
-            n = try self.read()
-            if n > Socket.CR { characters.append(Character(UnicodeScalar(n))) }
-        } while n != Socket.NL
-        return characters
-    }
-    
-    public func peername() throws -> String {
-        var addr = sockaddr(), len: socklen_t = socklen_t(MemoryLayout<sockaddr>.size)
-        if getpeername(self.socketFileDescriptor, &addr, &len) != 0 {
-            throw SocketError.getPeerNameFailed(Errno.description())
-        }
-        var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
-        if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
-            throw SocketError.getNameInfoFailed(Errno.description())
-        }
-        return String(cString: hostBuffer)
-    }
-    
-    public class func setNoSigPipe(_ socket: Int32) {
-        #if os(Linux)
-            // There is no SO_NOSIGPIPE in Linux (nor some other systems). You can instead use the MSG_NOSIGNAL flag when calling send(),
-            // or use signal(SIGPIPE, SIG_IGN) to make your entire application ignore SIGPIPE.
-        #else
-            // Prevents crashes when blocking calls are pending and the app is paused ( via Home button ).
-            var no_sig_pipe: Int32 = 1
-            setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout<Int32>.size))
-        #endif
-    }
-    
-    public class func close(_ socket: Int32) {
-        #if os(Linux)
-            let _ = Glibc.close(socket)
-        #else
-            let _ = Darwin.close(socket)
-        #endif
-    }
-}
-
-public func == (socket1: Socket, socket2: Socket) -> Bool {
-    return socket1.socketFileDescriptor == socket2.socketFileDescriptor
-}

+ 81 - 0
Sources/Swifter.swift

@@ -0,0 +1,81 @@
+//
+//  HttpServer.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class Swifter {
+    
+    public static let version = "2.0.0a"
+    
+    private let router: Router<(([String: String], Request, @escaping ((Response) -> Void)) -> Void)>
+    private let server: Server
+    
+    public var notFoundHandler: ((Request) -> Response)?
+    
+    public var middleware = Array<((Request) -> Response?)>()
+    
+    public init(_ port: in_port_t = 8080) throws {
+        self.router = Router()
+        self.server = try Server(port)
+    }
+    
+    public func get(_ path: String, _ closure: @escaping (([String: String], Request, @escaping ((Response) -> Void)) -> Void)) {
+        router.attach("GET", path: path, handler: closure)
+    }
+    
+    public func post(_ path: String, _ closure: @escaping (([String: String], Request, @escaping ((Response) -> Void)) -> Void)) {
+        router.attach("POST", path: path, handler: closure)
+    }
+    
+    public func put(_ path: String, _ closure: @escaping (([String: String], Request, @escaping ((Response) -> Void)) -> Void)) {
+        router.attach("PUT", path: path, handler: closure)
+    }
+    
+    public func delete(_ path: String, _ closure: @escaping (([String: String], Request, @escaping ((Response) -> Void)) -> Void)) {
+        router.attach("DELETE", path: path, handler: closure)
+    }
+    
+    public func options(_ path: String, _ closure: @escaping (([String: String], Request, @escaping ((Response) -> Void)) -> Void)) {
+        router.attach("OPTIONS", path: path, handler: closure)
+    }
+    
+    public subscript(path: String) -> (([String: String], Request, @escaping ((Response) -> Void)) -> Void)? {
+        set {
+            router.attach(nil, path: path, handler: newValue)
+        }
+        get { return nil }
+    }
+    
+    public var routes: [String] {
+        return router.routes();
+    }
+    
+    public func loop() throws {
+        try self.server.serve { request, responder in
+            var middlewareResponse: Response? = nil
+            for layer in self.middleware {
+                if let responseFound = layer(request) {
+                    middlewareResponse = responseFound
+                    break
+                }
+            }
+            if let middlewareResponseFound = middlewareResponse {
+                responder(middlewareResponseFound)
+            } else {
+                if let (params, response) = self.router.route(request.method, path: request.path) {
+                    response(params, request, responder)
+                } else {
+                    if let notFoundHandler = self.notFoundHandler {
+                        responder(notFoundHandler(request))
+                    } else {
+                        responder(Response(404))
+                    }
+                }
+            }
+        }
+    }
+}

+ 33 - 0
Sources/Tcp.swift

@@ -0,0 +1,33 @@
+//
+//  OS.swift
+//  Swifter
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public protocol TcpServer: class {
+    
+    init(_ port: in_port_t, forceIPv4: Bool, bindAddress: String?) throws
+    
+    func wait(_ callback: ((TcpServerEvent) -> Void)) throws
+    
+    func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws
+    
+    func finish(_ socket: Int32)
+}
+
+public enum TcpWriteDoneAction {
+    
+    case `continue`, terminate
+}
+
+public enum TcpServerEvent {
+    
+    case connect(String, Int32)
+    
+    case disconnect(String, Int32)
+    
+    case data(String, Int32, ArraySlice<UInt8>)
+}

+ 128 - 231
Sources/WebSockets.swift

@@ -1,275 +1,172 @@
 //
-//  HttpHandlers+WebSockets.swift
+//  WebSockets.swift
 //  Swifter
 //
-//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
+//  Copyright © 2016 Damian Kolakowski. All rights reserved.
 //
-
 import Foundation
 
+public enum WebSocketError: Error {
+    case unknownOpCode(String)
+    case unMaskedFrame
+    case notImplemented(String)
+}
 
-public func websocket(
-      _ text: ((WebSocketSession, String) -> Void)?,
-    _ binary: ((WebSocketSession, [UInt8]) -> Void)?) -> ((HttpRequest) -> HttpResponse) {
-    return { r in
-        guard r.hasTokenForHeader("upgrade", token: "websocket") else {
-            return .badRequest(.text("Invalid value of 'Upgrade' header: \(r.headers["upgrade"])"))
+public enum WebsocketEvent {
+    case disconnected(Int, String)
+    case text(String)
+    case binary([UInt8])
+}
+
+public class WebsocketResponse: Response {
+    
+    public init(_ request: Request, _ closure: @escaping ((WebsocketEvent) -> Void)) {
+        
+        super.init()
+        
+        guard request.hasToken("websocket", forHeader: "upgrade") else {
+            self.status = Status.badRequest.rawValue
+            self.body = [UInt8](("Invalid value of 'Upgrade' header.").utf8)
+            return
         }
-        guard r.hasTokenForHeader("connection", token: "upgrade") else {
-            return .badRequest(.text("Invalid value of 'Connection' header: \(r.headers["connection"])"))
+        
+        guard request.hasToken("upgrade", forHeader: "connection") else {
+            self.status = Status.badRequest.rawValue
+            self.body = [UInt8](("Invalid value of 'Connection' header.").utf8)
+            return
         }
-        guard let secWebSocketKey = r.headers["sec-websocket-key"] else {
-            return .badRequest(.text("Invalid value of 'Sec-Websocket-Key' header: \(r.headers["sec-websocket-key"])"))
-        }
-        let protocolSessionClosure: ((Socket) -> Void) = { socket in
-            let session = WebSocketSession(socket)
-            var fragmentedOpCode = WebSocketSession.OpCode.close
-            var payload = [UInt8]() // Used for fragmented frames.
-            
-            func handleTextPayload(_ frame: WebSocketSession.Frame) throws {
-                if let handleText = text {
-                    if frame.fin {
-                        if payload.count > 0 {
-                            throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.")
-                        }
-                        var textFramePayload = frame.payload.map { Int8(bitPattern: $0) }
-                        textFramePayload.append(0)
-                        if let text = String(validatingUTF8: textFramePayload) {
-                            handleText(session, text)
-                        } else {
-                            throw WebSocketSession.WsError.invalidUTF8("")
-                        }
-                    } else {
-                        payload.append(contentsOf: frame.payload)
-                        fragmentedOpCode = .text
-                    }
-                }
-            }
-            
-            func handleBinaryPayload(_ frame: WebSocketSession.Frame) throws {
-                if let handleBinary = binary {
-                    if frame.fin {
-                        if payload.count > 0 {
-                            throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.")
-                        }
-                        handleBinary(session, frame.payload)
-                    } else {
-                        payload.append(contentsOf: frame.payload)
-                        fragmentedOpCode = .binary
-                    }
-                }
-            }
-            
-            func handleOperationCode(_ frame: WebSocketSession.Frame) throws {
-                switch frame.opcode {
-                case .continue:
-                    // There is no message to continue, failed immediatelly.
-                    if fragmentedOpCode == .close {
-                        socket.close()
-                    }
-                    frame.opcode = fragmentedOpCode
-                    if frame.fin {
-                        payload.append(contentsOf: frame.payload)
-                        frame.payload = payload
-                        // Clean the buffer.
-                        payload = []
-                        // Reset the OpCode.
-                        fragmentedOpCode = WebSocketSession.OpCode.close
-                    }
-                    try handleOperationCode(frame)
-                case .text:
-                    try handleTextPayload(frame)
-                case .binary:
-                    try handleBinaryPayload(frame)
-                case .close:
-                    throw WebSocketSession.Control.close
-                case .ping:
-                    if frame.payload.count > 125 {
-                        throw WebSocketSession.WsError.protocolError("Payload gretter than 125 octets.")
-                    } else {
-                        session.writeFrame(ArraySlice(frame.payload), .pong)
-                    }
-                case .pong:
-                    break
-                }
-            }
-            
-            do {
-                while true {
-                    let frame = try session.readFrame()
-                    try handleOperationCode(frame)
-                }
-            } catch let error {
-                switch error {
-                case WebSocketSession.Control.close:
-                    // Normal close
-                    break
-                case WebSocketSession.WsError.unknownOpCode:
-                    print("Unknown Op Code: \(error)")
-                case WebSocketSession.WsError.unMaskedFrame:
-                    print("Unmasked frame: \(error)")
-                case WebSocketSession.WsError.invalidUTF8:
-                    print("Invalid UTF8 character: \(error)")
-                case WebSocketSession.WsError.protocolError:
-                    print("Protocol error: \(error)")
-                default:
-                    print("Unkown error \(error)")
-                }
-                // If an error occurs, send the close handshake.
-                session.writeCloseFrame()
-            }
+        
+        guard let (_, secWebSocketKey) = request.headers.filter({ $0.0 == "sec-websocket-key" }).first else {
+            self.status = Status.badRequest.rawValue
+            self.body = [UInt8](("Invalid value of 'Sec-Websocket-Key' header.").utf8)
+            return
         }
+        
         guard let secWebSocketAccept = String.toBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").sha1()) else {
-            return HttpResponse.internalServerError
+            self.status = Status.internalServerError.rawValue
+            self.body = [UInt8](("Failed to convert websocket key value to base64.").utf8)
+            return
         }
-        let headers = ["Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept]
-        return HttpResponse.switchProtocols(headers, protocolSessionClosure)
+        
+        self.status = Status.switchingProtocols.rawValue
+        
+        self.headers = [ ("Upgrade", "WebSocket"), ("Connection", "Upgrade"), ("Sec-WebSocket-Accept", secWebSocketAccept)]
+        
+        self.processingSuccesor = WebsocketDataPorcessor(WebsocketFramesProcessor(closure))
     }
 }
 
-public class WebSocketSession: Hashable, Equatable  {
-    
-    public enum WsError: Error { case unknownOpCode(String), unMaskedFrame(String), protocolError(String), invalidUTF8(String) }
-    public enum OpCode: UInt8 { case `continue` = 0x00, close = 0x08, ping = 0x09, pong = 0x0A, text = 0x01, binary = 0x02 }
-    public enum Control: Error { case close }
-    
-    public class Frame {
-        public var opcode = OpCode.close
-        public var fin = false
-        public var rsv1: UInt8 = 0
-        public var rsv2: UInt8 = 0
-        public var rsv3: UInt8 = 0
-        public var payload = [UInt8]()
-    }
-
-    let socket: Socket
+public class WebSocketFrame {
     
-    public init(_ socket: Socket) {
-        self.socket = socket
+    public enum OpCode: UInt8 {
+        case `continue` = 0x00
+        case close = 0x08
+        case ping = 0x09
+        case pong = 0x0A
+        case text = 0x01
+        case binary = 0x02
     }
     
-    deinit {
-        writeCloseFrame()
-        socket.close()
+    public init(_ opcode: OpCode, _ payload: [UInt8]) {
+        self.opcode = opcode
+        self.payload = payload
     }
     
-    public func writeText(_ text: String) -> Void {
-        self.writeFrame(ArraySlice(text.utf8), OpCode.text)
-    }
+    public let opcode: OpCode
+    public let payload: [UInt8]
+}
 
-    public func writeBinary(_ binary: [UInt8]) -> Void {
-        self.writeBinary(ArraySlice(binary))
-    }
+public class WebsocketFramesProcessor {
     
-    public func writeBinary(_ binary: ArraySlice<UInt8>) -> Void {
-        self.writeFrame(binary, OpCode.binary)
+    private let closure: ((WebsocketEvent) -> Void)
+    
+    public init(_ closure: @escaping ((WebsocketEvent) -> Void)) {
+        self.closure = closure
     }
     
-    public func writeFrame(_ data: ArraySlice<UInt8>, _ op: OpCode, _ fin: Bool = true) {
-        let finAndOpCode = UInt8(fin ? 0x80 : 0x00) | op.rawValue
-        let maskAndLngth = encodeLengthAndMaskFlag(UInt64(data.count), false)
-        do {
-            try self.socket.writeUInt8([finAndOpCode])
-            try self.socket.writeUInt8(maskAndLngth)
-            try self.socket.writeUInt8(data)
-        } catch {
-            print(error)
+    public func process(_ frame: WebSocketFrame) throws {
+        switch frame.opcode {
+        case .text:
+            if let text = String(bytes: frame.payload, encoding: .utf8) {
+                self.closure(.text(text))
+            } else {
+                print("Invalid payload (not utf8): \(frame.payload)")
+            }
+        case .binary:
+            self.closure(.binary(frame.payload))
+        default:
+            throw WebSocketError.notImplemented("Not able to handle: \(frame.opcode.rawValue)")
         }
     }
+}
+
+public class WebsocketDataPorcessor: IncomingDataProcessor {
     
-    public func writeCloseFrame() {
-        writeFrame(ArraySlice("".utf8), .close)
-    }
+    private let framesProcessor: WebsocketFramesProcessor
     
-    private func encodeLengthAndMaskFlag(_ len: UInt64, _ masked: Bool) -> [UInt8] {
-        let encodedLngth = UInt8(masked ? 0x80 : 0x00)
-        var encodedBytes = [UInt8]()
-        switch len {
-        case 0...125:
-            encodedBytes.append(encodedLngth | UInt8(len));
-        case 126...UInt64(UINT16_MAX):
-            encodedBytes.append(encodedLngth | 0x7E);
-            encodedBytes.append(UInt8(len >> 8 & 0xFF));
-            encodedBytes.append(UInt8(len >> 0 & 0xFF));
-        default:
-            encodedBytes.append(encodedLngth | 0x7F);
-            encodedBytes.append(UInt8(len >> 56 & 0xFF));
-            encodedBytes.append(UInt8(len >> 48 & 0xFF));
-            encodedBytes.append(UInt8(len >> 40 & 0xFF));
-            encodedBytes.append(UInt8(len >> 32 & 0xFF));
-            encodedBytes.append(UInt8(len >> 24 & 0xFF));
-            encodedBytes.append(UInt8(len >> 16 & 0xFF));
-            encodedBytes.append(UInt8(len >> 08 & 0xFF));
-            encodedBytes.append(UInt8(len >> 00 & 0xFF));
-        }
-        return encodedBytes
+    public init(_ framesProcessor: WebsocketFramesProcessor) {
+        self.framesProcessor = framesProcessor
     }
     
-    public func readFrame() throws -> Frame {
-        let frm = Frame()
-        let fst = try socket.read()
-        frm.fin = fst & 0x80 != 0
-        frm.rsv1 = fst & 0x40
-        frm.rsv2 = fst & 0x20
-        frm.rsv3 = fst & 0x10
-        guard frm.rsv1 == 0 && frm.rsv2 == 0 && frm.rsv3 == 0
-            else {
-            throw WsError.protocolError("Reserved frame bit has not been negocitated.")
-        }
-        let opc = fst & 0x0F
-        guard let opcode = OpCode(rawValue: opc) else {
+    private var stack = [UInt8]()
+    
+    public func process(_ chunk: ArraySlice<UInt8>) throws {
+        
+        stack.append(contentsOf: chunk)
+        
+        guard stack.count > 1 else { return }
+        
+        _ = stack[0] & 0x80 != 0
+        let opc = stack[0] & 0x0F
+        
+        guard let opcode = WebSocketFrame.OpCode(rawValue: opc) else {
             // "If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_."
             // http://tools.ietf.org/html/rfc6455#section-5.2 ( Page 29 )
-            throw WsError.unknownOpCode("\(opc)")
-        }
-        if frm.fin == false {
-            switch opcode {
-            case .ping, .pong, .close:
-                // Control frames must not be fragmented
-                // https://tools.ietf.org/html/rfc6455#section-5.5 ( Page 35 )
-                throw WsError.protocolError("Control frames must not be fragmented.")
-            default:
-                break
-            }
+            throw WebSocketError.unknownOpCode("\(opc)")
         }
-        frm.opcode = opcode
-        let sec = try socket.read()
-        let msk = sec & 0x80 != 0
+        
+        let msk = stack[1] & 0x80 != 0
+        
         guard msk else {
-            // "...a client MUST mask all frames that it sends to the server."
+            // "...a client MUST mask all frames that it sends to the serve.."
             // http://tools.ietf.org/html/rfc6455#section-5.1
-            throw WsError.unMaskedFrame("A client must mask all frames that it sends to the server.")
+            throw WebSocketError.unMaskedFrame
         }
-        var len = UInt64(sec & 0x7F)
+        
+        var len = UInt64(stack[1] & 0x7F)
+        var offset = 2
         if len == 0x7E {
-            let b0 = UInt64(try socket.read())
-            let b1 = UInt64(try socket.read())
+            guard stack.count > 3 else { return }
+            let b0 = UInt64(stack[2])
+            let b1 = UInt64(stack[3])
             len = UInt64(littleEndian: b0 << 8 | b1)
+            offset = 4
         } else if len == 0x7F {
-            let b0 = UInt64(try socket.read())
-            let b1 = UInt64(try socket.read())
-            let b2 = UInt64(try socket.read())
-            let b3 = UInt64(try socket.read())
-            let b4 = UInt64(try socket.read())
-            let b5 = UInt64(try socket.read())
-            let b6 = UInt64(try socket.read())
-            let b7 = UInt64(try socket.read())
+            guard stack.count > 9 else { return }
+            let b0 = UInt64(stack[2])
+            let b1 = UInt64(stack[3])
+            let b2 = UInt64(stack[4])
+            let b3 = UInt64(stack[5])
+            let b4 = UInt64(stack[6])
+            let b5 = UInt64(stack[7])
+            let b6 = UInt64(stack[8])
+            let b7 = UInt64(stack[9])
             len = UInt64(littleEndian: b0 << 54 | b1 << 48 | b2 << 40 | b3 << 32 | b4 << 24 | b5 << 16 | b6 << 8 | b7)
+            offset = 10
         }
-        let mask = [try socket.read(), try socket.read(), try socket.read(), try socket.read()]
-        for i in 0..<len {
-            frm.payload.append(try socket.read() ^ mask[Int(i % 4)])
-        }
-        return frm
-    }
-    
-    public var hashValue: Int {
-        get {
-            return socket.hashValue
+        
+        guard (len + UInt64(offset) + 4) >= UInt64(stack.count) else {
+            return
         }
+        
+        let mask = [stack[offset], stack[offset+1], stack[offset+2], stack[offset+3]]
+        
+        offset = offset + mask.count
+        
+        let payload = stack[offset..<(offset + Int(len /* //TODO fix this */))].enumerated().map { $0.element ^ mask[Int($0.offset % 4)] }
+        
+        stack.removeFirst(offset+Int(len))
+        
+        try framesProcessor.process(WebSocketFrame(opcode, payload))
     }
 }
-
-public func ==(webSocketSession1: WebSocketSession, webSocketSession2: WebSocketSession) -> Bool {
-    return webSocketSession1.socket == webSocketSession2.socket
-}

+ 149 - 129
XCode/Swifter.xcodeproj/project.pbxproj

@@ -12,71 +12,88 @@
 		0858E7F81D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
 		0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
 		2659FC1A1DADC077003F3930 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
-		269B47881D3AAAE20042D137 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
-		269B47891D3AAAE20042D137 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
-		269B478A1D3AAAE20042D137 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
-		269B478B1D3AAAE20042D137 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
+		269B47891D3AAAE20042D137 /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
 		269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
-		269B478D1D3AAAE20042D137 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
-		269B478E1D3AAAE20042D137 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
+		269B478E1D3AAAE20042D137 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
 		269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
-		269B47901D3AAAE20042D137 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
-		269B47921D3AAAE20042D137 /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
-		269B47931D3AAAE20042D137 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
-		269B47941D3AAAE20042D137 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
+		269B47901D3AAAE20042D137 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
 		269B47951D3AAAE20042D137 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
-		269B47961D3AAAE20042D137 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
+		269B47961D3AAAE20042D137 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
 		269B47971D3AAAE20042D137 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
 		269B47981D3AAAE20042D137 /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
-		269B47991D3AAAE20042D137 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		269B47991D3AAAE20042D137 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* Base64.swift */; };
 		269B47A71D3AAC4F0042D137 /* SwiftertvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 269B47A51D3AAC4F0042D137 /* SwiftertvOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893EA1C05127900A29F63 /* SwifteriOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893E91C05127900A29F63 /* SwifteriOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893FE1C0512C400A29F63 /* SwifterMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893FD1C0512C400A29F63 /* SwifterMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */; };
+		7C0324271E51D20700325E4F /* Http.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241B1E51CF6600325E4F /* Http.swift */; };
+		7C0324281E51D20700325E4F /* Http.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241B1E51CF6600325E4F /* Http.swift */; };
+		7C0324291E51D20700325E4F /* Http.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241B1E51CF6600325E4F /* Http.swift */; };
+		7C03242A1E51D20B00325E4F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241D1E51CFEF00325E4F /* Misc.swift */; };
+		7C03242B1E51D20B00325E4F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241D1E51CFEF00325E4F /* Misc.swift */; };
+		7C03242C1E51D20C00325E4F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241D1E51CFEF00325E4F /* Misc.swift */; };
+		7C03242D1E51D20D00325E4F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241D1E51CFEF00325E4F /* Misc.swift */; };
+		7C03242E1E51D20D00325E4F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241D1E51CFEF00325E4F /* Misc.swift */; };
+		7C03242F1E51D21000325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
+		7C0324301E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
+		7C0324311E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
+		7C0324321E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
+		7C0324331E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
+		7C0324341E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324351E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324361E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324371E51D21600325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324381E51D21600325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324391E51D21800325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
+		7C03243A1E51D21900325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
+		7C03243B1E51D21900325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
+		7C03243C1E51D21A00325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
+		7C03243D1E51D21A00325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
+		7C03243E1E51D21C00325E4F /* MacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324211E51D09900325E4F /* MacOS.swift */; };
+		7C03243F1E51D21D00325E4F /* MacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324211E51D09900325E4F /* MacOS.swift */; };
+		7C0324401E51D21D00325E4F /* MacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324211E51D09900325E4F /* MacOS.swift */; };
+		7C0324411E51D21D00325E4F /* MacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324211E51D09900325E4F /* MacOS.swift */; };
+		7C0324421E51D21E00325E4F /* MacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324211E51D09900325E4F /* MacOS.swift */; };
+		7C0324431E51D24A00325E4F /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
+		7C0324441E51D24C00325E4F /* Http.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241B1E51CF6600325E4F /* Http.swift */; };
+		7C0324451E51D24C00325E4F /* Http.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241B1E51CF6600325E4F /* Http.swift */; };
+		7C0324471E51D36900325E4F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324461E51D36900325E4F /* Error.swift */; };
+		7C0324481E51D36900325E4F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324461E51D36900325E4F /* Error.swift */; };
+		7C0324491E51D36900325E4F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324461E51D36900325E4F /* Error.swift */; };
+		7C03244A1E51D36900325E4F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324461E51D36900325E4F /* Error.swift */; };
+		7C03244B1E51D36900325E4F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324461E51D36900325E4F /* Error.swift */; };
 		7C377E181D964B96009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
 		7C377E191D964B9F009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
-		7C458EFC1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
-		7C458EFD1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
-		7C458EFE1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
 		7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
 		7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */; };
-		7C76B29F1D369BEC00D35BFB /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
-		7C76B2A01D369BEC00D35BFB /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
 		7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
 		7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
-		7C76B70D1D2C456A0030FC98 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
-		7C76B70E1D2C456B0030FC98 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
+		7C76B70D1D2C456A0030FC98 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
+		7C76B70E1D2C456B0030FC98 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
 		7C76B7111D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
 		7C76B7121D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
-		7C76B7131D2C45730030FC98 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
-		7C76B7141D2C45730030FC98 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
 		7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
 		7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
-		7C76B7171D2C45780030FC98 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
-		7C76B7181D2C45790030FC98 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
-		7C76B7191D2C457C0030FC98 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
-		7C76B71A1D2C457C0030FC98 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
-		7C76B71B1D2C457E0030FC98 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
-		7C76B71C1D2C457E0030FC98 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
-		7C76B71D1D2C45820030FC98 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
-		7C76B71E1D2C45820030FC98 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
-		7C76B71F1D2C45840030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
-		7C76B7201D2C45840030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
-		7C76B7211D2C45870030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
-		7C76B7221D2C45870030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
-		7C76B7231D2C45890030FC98 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
-		7C76B7241D2C458A0030FC98 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
-		7C76B7251D2C458C0030FC98 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
-		7C76B7261D2C458D0030FC98 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		7C76B7191D2C457C0030FC98 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
+		7C76B71A1D2C457C0030FC98 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
+		7C76B71B1D2C457E0030FC98 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
+		7C76B71C1D2C457E0030FC98 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
+		7C76B7211D2C45870030FC98 /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
+		7C76B7221D2C45870030FC98 /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
+		7C76B7251D2C458C0030FC98 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* Base64.swift */; };
+		7C76B7261D2C458D0030FC98 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* Base64.swift */; };
 		7C76B7271D2C458F0030FC98 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
 		7C76B7281D2C458F0030FC98 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
 		7C76B7291D2C45920030FC98 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
 		7C76B72A1D2C45920030FC98 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
-		7C76B72B1D2C45940030FC98 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
-		7C76B72C1D2C45950030FC98 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
+		7CAB377C1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
+		7CAB377D1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
+		7CAB377E1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
+		7CAB377F1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
+		7CAB37801E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
 		7CB102E01A17381D00CBA3B4 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7CCB8C5E1D97B852008B9712 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
 		7CCB8C621D97B8E9008B9712 /* SwifterTestsHttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */; };
@@ -89,21 +106,13 @@
 		7CDAB8141BE2A1D400C8A977 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7CDAB80F1BE2A1D400C8A977 /* Images.xcassets */; };
 		7CDAB8161BE2A1D400C8A977 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB8111BE2A1D400C8A977 /* ViewController.swift */; };
 		7CEBB86F1D94612D00370A6B /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
-		7CEBB8701D94612D00370A6B /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
 		7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
-		7CEBB8721D94612D00370A6B /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
-		7CEBB8731D94612D00370A6B /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
-		7CEBB8741D94612D00370A6B /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
-		7CEBB8751D94612D00370A6B /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
-		7CEBB8761D94612D00370A6B /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
-		7CEBB8771D94612D00370A6B /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
-		7CEBB8781D94612D00370A6B /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
-		7CEBB8791D94612D00370A6B /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
-		7CEBB87A1D94612D00370A6B /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
-		7CEBB87B1D94612D00370A6B /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		7CEBB8731D94612D00370A6B /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
+		7CEBB8741D94612D00370A6B /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
+		7CEBB8771D94612D00370A6B /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
+		7CEBB87B1D94612D00370A6B /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* Base64.swift */; };
 		7CEBB87C1D94612D00370A6B /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
 		7CEBB87D1D94612D00370A6B /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
-		7CEBB87E1D94612D00370A6B /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
 		7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
 /* End PBXBuildFile section */
 
@@ -157,29 +166,29 @@
 		7AE893FD1C0512C400A29F63 /* SwifterMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwifterMac.h; sourceTree = "<group>"; };
 		7AE893FF1C0512C400A29F63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
+		7C03241B1E51CF6600325E4F /* Http.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Http.swift; sourceTree = "<group>"; };
+		7C03241D1E51CFEF00325E4F /* Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Misc.swift; sourceTree = "<group>"; };
+		7C03241F1E51D07A00325E4F /* Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
+		7C0324211E51D09900325E4F /* MacOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacOS.swift; sourceTree = "<group>"; };
+		7C0324231E51D0A400325E4F /* Linux.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linux.swift; sourceTree = "<group>"; };
+		7C0324251E51D0F900325E4F /* Tcp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tcp.swift; sourceTree = "<group>"; };
+		7C0324461E51D36900325E4F /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
 		7C377E161D964B6A009C6148 /* String+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+File.swift"; sourceTree = "<group>"; };
-		7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+Server.swift"; sourceTree = "<group>"; };
 		7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsWebSocketSession.swift; sourceTree = "<group>"; };
-		7C76B29E1D369BEC00D35BFB /* Socket+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+File.swift"; sourceTree = "<group>"; };
 		7C76B2A11D369C9D00D35BFB /* Errno.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errno.swift; sourceTree = "<group>"; };
-		7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = "<group>"; };
+		7C76B6EA1D2C44F30030FC98 /* Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Demo.swift; sourceTree = "<group>"; };
 		7C76B6EC1D2C44F30030FC98 /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = "<group>"; };
-		7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpParser.swift; sourceTree = "<group>"; };
 		7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
-		7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = "<group>"; };
-		7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = "<group>"; };
-		7C76B6F11D2C44F30030FC98 /* HttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServer.swift; sourceTree = "<group>"; };
-		7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerIO.swift; sourceTree = "<group>"; };
-		7C76B6F31D2C44F30030FC98 /* Process.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = "<group>"; };
-		7C76B6F41D2C44F30030FC98 /* Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scopes.swift; sourceTree = "<group>"; };
-		7C76B6F51D2C44F30030FC98 /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = "<group>"; };
-		7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BASE64.swift"; sourceTree = "<group>"; };
+		7C76B6F01D2C44F30030FC98 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
+		7C76B6F11D2C44F30030FC98 /* Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifter.swift; sourceTree = "<group>"; };
+		7C76B6F41D2C44F30030FC98 /* Html.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Html.swift; sourceTree = "<group>"; };
+		7C76B6F61D2C44F30030FC98 /* Base64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base64.swift; sourceTree = "<group>"; };
 		7C76B6F71D2C44F30030FC98 /* String+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = "<group>"; };
 		7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SHA1.swift"; sourceTree = "<group>"; };
-		7C76B6F91D2C44F30030FC98 /* WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSockets.swift; sourceTree = "<group>"; };
 		7C839B6E19422CFF003A6950 /* SwifterSampleiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwifterSampleiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813B19A2EA8D0030B30D /* SwifterSampleOSX */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwifterSampleOSX; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813D19A2EA8D0030B30D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
+		7CAB377B1E51E7D300FED085 /* WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSockets.swift; sourceTree = "<group>"; };
 		7CB102DF1A17381D00CBA3B4 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
 		7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsHttpRouter.swift; sourceTree = "<group>"; };
 		7CCD875C1C66099B0068099B /* SwifteriOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifteriOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -279,24 +288,24 @@
 		7C76B6E91D2C44F30030FC98 /* Sources */ = {
 			isa = PBXGroup;
 			children = (
-				7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */,
+				7C76B6F61D2C44F30030FC98 /* Base64.swift */,
+				7C76B2A11D369C9D00D35BFB /* Errno.swift */,
+				7C0324461E51D36900325E4F /* Error.swift */,
+				7C03241D1E51CFEF00325E4F /* Misc.swift */,
+				7C76B6F41D2C44F30030FC98 /* Html.swift */,
+				7C76B6F01D2C44F30030FC98 /* Router.swift */,
+				7C76B6F11D2C44F30030FC98 /* Swifter.swift */,
+				7C03241F1E51D07A00325E4F /* Server.swift */,
+				7C03241B1E51CF6600325E4F /* Http.swift */,
+				7C0324231E51D0A400325E4F /* Linux.swift */,
+				7C0324251E51D0F900325E4F /* Tcp.swift */,
+				7C0324211E51D09900325E4F /* MacOS.swift */,
+				7C76B6EA1D2C44F30030FC98 /* Demo.swift */,
 				7C76B6EC1D2C44F30030FC98 /* Files.swift */,
-				7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */,
+				7CAB377B1E51E7D300FED085 /* WebSockets.swift */,
 				7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */,
-				7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */,
-				7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */,
-				7C76B6F11D2C44F30030FC98 /* HttpServer.swift */,
-				7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */,
-				7C76B6F31D2C44F30030FC98 /* Process.swift */,
-				7C76B6F41D2C44F30030FC98 /* Scopes.swift */,
-				7C76B6F51D2C44F30030FC98 /* Socket.swift */,
-				7C76B29E1D369BEC00D35BFB /* Socket+File.swift */,
-				7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */,
-				7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */,
 				7C76B6F71D2C44F30030FC98 /* String+Misc.swift */,
 				7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */,
-				7C76B6F91D2C44F30030FC98 /* WebSockets.swift */,
-				7C76B2A11D369C9D00D35BFB /* Errno.swift */,
 				7C377E161D964B6A009C6148 /* String+File.swift */,
 			);
 			name = Sources;
@@ -656,25 +665,25 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				269B47881D3AAAE20042D137 /* HttpResponse.swift in Sources */,
-				269B47891D3AAAE20042D137 /* Scopes.swift in Sources */,
-				269B478A1D3AAAE20042D137 /* Process.swift in Sources */,
-				269B478B1D3AAAE20042D137 /* HttpParser.swift in Sources */,
+				7CAB377E1E51E7D300FED085 /* WebSockets.swift in Sources */,
+				269B47891D3AAAE20042D137 /* Html.swift in Sources */,
 				269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */,
-				269B478D1D3AAAE20042D137 /* WebSockets.swift in Sources */,
-				269B478E1D3AAAE20042D137 /* HttpServer.swift in Sources */,
+				7C03243B1E51D21900325E4F /* Linux.swift in Sources */,
+				7C0324311E51D21100325E4F /* Server.swift in Sources */,
+				269B478E1D3AAAE20042D137 /* Swifter.swift in Sources */,
 				269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */,
-				269B47901D3AAAE20042D137 /* DemoServer.swift in Sources */,
-				269B47921D3AAAE20042D137 /* Socket+File.swift in Sources */,
-				269B47931D3AAAE20042D137 /* Socket.swift in Sources */,
-				269B47941D3AAAE20042D137 /* HttpServerIO.swift in Sources */,
+				269B47901D3AAAE20042D137 /* Demo.swift in Sources */,
+				7C0324291E51D20700325E4F /* Http.swift in Sources */,
 				269B47951D3AAAE20042D137 /* Files.swift in Sources */,
 				2659FC1A1DADC077003F3930 /* String+File.swift in Sources */,
-				269B47961D3AAAE20042D137 /* HttpRouter.swift in Sources */,
+				7C0324401E51D21D00325E4F /* MacOS.swift in Sources */,
+				269B47961D3AAAE20042D137 /* Router.swift in Sources */,
 				269B47971D3AAAE20042D137 /* String+SHA1.swift in Sources */,
-				7C458EFE1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				7C03242C1E51D20C00325E4F /* Misc.swift in Sources */,
 				269B47981D3AAAE20042D137 /* Errno.swift in Sources */,
-				269B47991D3AAAE20042D137 /* String+BASE64.swift in Sources */,
+				7C0324361E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324491E51D36900325E4F /* Error.swift in Sources */,
+				269B47991D3AAAE20042D137 /* Base64.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -682,25 +691,25 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7CAB377C1E51E7D300FED085 /* WebSockets.swift in Sources */,
 				7C377E191D964B9F009C6148 /* String+File.swift in Sources */,
-				7C76B7171D2C45780030FC98 /* HttpResponse.swift in Sources */,
-				7C76B7211D2C45870030FC98 /* Scopes.swift in Sources */,
-				7C76B71F1D2C45840030FC98 /* Process.swift in Sources */,
-				7C76B7131D2C45730030FC98 /* HttpParser.swift in Sources */,
+				7C76B7211D2C45870030FC98 /* Html.swift in Sources */,
 				7C76B7271D2C458F0030FC98 /* String+Misc.swift in Sources */,
-				7C76B72B1D2C45940030FC98 /* WebSockets.swift in Sources */,
-				7C76B71B1D2C457E0030FC98 /* HttpServer.swift in Sources */,
+				7C03243D1E51D21A00325E4F /* Linux.swift in Sources */,
+				7C03242F1E51D21000325E4F /* Server.swift in Sources */,
+				7C76B71B1D2C457E0030FC98 /* Swifter.swift in Sources */,
 				7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */,
-				7C76B70D1D2C456A0030FC98 /* DemoServer.swift in Sources */,
-				7C76B29F1D369BEC00D35BFB /* Socket+File.swift in Sources */,
-				7C76B7231D2C45890030FC98 /* Socket.swift in Sources */,
-				7C76B71D1D2C45820030FC98 /* HttpServerIO.swift in Sources */,
+				7C0324271E51D20700325E4F /* Http.swift in Sources */,
+				7C76B70D1D2C456A0030FC98 /* Demo.swift in Sources */,
 				7C76B7111D2C45710030FC98 /* Files.swift in Sources */,
-				7C76B7191D2C457C0030FC98 /* HttpRouter.swift in Sources */,
+				7C0324421E51D21E00325E4F /* MacOS.swift in Sources */,
+				7C76B7191D2C457C0030FC98 /* Router.swift in Sources */,
 				7C76B7291D2C45920030FC98 /* String+SHA1.swift in Sources */,
-				7C458EFC1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				7C03242A1E51D20B00325E4F /* Misc.swift in Sources */,
 				7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */,
-				7C76B7251D2C458C0030FC98 /* String+BASE64.swift in Sources */,
+				7C0324341E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324471E51D36900325E4F /* Error.swift in Sources */,
+				7C76B7251D2C458C0030FC98 /* Base64.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -708,25 +717,25 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7CAB377D1E51E7D300FED085 /* WebSockets.swift in Sources */,
 				7C377E181D964B96009C6148 /* String+File.swift in Sources */,
-				7C76B7181D2C45790030FC98 /* HttpResponse.swift in Sources */,
-				7C76B7221D2C45870030FC98 /* Scopes.swift in Sources */,
-				7C76B7201D2C45840030FC98 /* Process.swift in Sources */,
-				7C76B7141D2C45730030FC98 /* HttpParser.swift in Sources */,
+				7C76B7221D2C45870030FC98 /* Html.swift in Sources */,
 				7C76B7281D2C458F0030FC98 /* String+Misc.swift in Sources */,
-				7C76B72C1D2C45950030FC98 /* WebSockets.swift in Sources */,
-				7C76B71C1D2C457E0030FC98 /* HttpServer.swift in Sources */,
+				7C03243C1E51D21A00325E4F /* Linux.swift in Sources */,
+				7C0324301E51D21100325E4F /* Server.swift in Sources */,
+				7C76B71C1D2C457E0030FC98 /* Swifter.swift in Sources */,
 				7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */,
-				7C76B70E1D2C456B0030FC98 /* DemoServer.swift in Sources */,
-				7C76B2A01D369BEC00D35BFB /* Socket+File.swift in Sources */,
-				7C76B7241D2C458A0030FC98 /* Socket.swift in Sources */,
-				7C76B71E1D2C45820030FC98 /* HttpServerIO.swift in Sources */,
+				7C0324281E51D20700325E4F /* Http.swift in Sources */,
+				7C76B70E1D2C456B0030FC98 /* Demo.swift in Sources */,
 				7C76B7121D2C45710030FC98 /* Files.swift in Sources */,
-				7C76B71A1D2C457C0030FC98 /* HttpRouter.swift in Sources */,
+				7C0324411E51D21D00325E4F /* MacOS.swift in Sources */,
+				7C76B71A1D2C457C0030FC98 /* Router.swift in Sources */,
 				7C76B72A1D2C45920030FC98 /* String+SHA1.swift in Sources */,
-				7C458EFD1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				7C03242B1E51D20B00325E4F /* Misc.swift in Sources */,
 				7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */,
-				7C76B7261D2C458D0030FC98 /* String+BASE64.swift in Sources */,
+				7C0324351E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324481E51D36900325E4F /* Error.swift in Sources */,
+				7C76B7261D2C458D0030FC98 /* Base64.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -751,11 +760,20 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7C03243A1E51D21900325E4F /* Linux.swift in Sources */,
+				7C0324321E51D21100325E4F /* Server.swift in Sources */,
 				7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */,
+				7CAB377F1E51E7D300FED085 /* WebSockets.swift in Sources */,
+				7C03244A1E51D36900325E4F /* Error.swift in Sources */,
 				0858E7F81D68BC2600491CD1 /* PingServer.swift in Sources */,
 				0858E7F41D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
+				7C0324441E51D24C00325E4F /* Http.swift in Sources */,
+				7C03242D1E51D20D00325E4F /* Misc.swift in Sources */,
+				7C0324371E51D21600325E4F /* Tcp.swift in Sources */,
+				7C03243F1E51D21D00325E4F /* MacOS.swift in Sources */,
 				7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
+				7C0324431E51D24A00325E4F /* Html.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -766,26 +784,26 @@
 				7CCB8C621D97B8E9008B9712 /* SwifterTestsHttpRouter.swift in Sources */,
 				7CCB8C5E1D97B852008B9712 /* String+File.swift in Sources */,
 				7CEBB86F1D94612D00370A6B /* Files.swift in Sources */,
-				7CEBB8701D94612D00370A6B /* HttpParser.swift in Sources */,
+				7C0324451E51D24C00325E4F /* Http.swift in Sources */,
 				7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */,
-				7CEBB8721D94612D00370A6B /* HttpResponse.swift in Sources */,
-				7CEBB8731D94612D00370A6B /* HttpRouter.swift in Sources */,
-				7CEBB8741D94612D00370A6B /* HttpServer.swift in Sources */,
-				7CEBB8751D94612D00370A6B /* HttpServerIO.swift in Sources */,
-				7CEBB8761D94612D00370A6B /* Process.swift in Sources */,
-				7CEBB8771D94612D00370A6B /* Scopes.swift in Sources */,
-				7CEBB8781D94612D00370A6B /* Socket.swift in Sources */,
-				7CEBB8791D94612D00370A6B /* Socket+File.swift in Sources */,
-				7CEBB87A1D94612D00370A6B /* Socket+Server.swift in Sources */,
-				7CEBB87B1D94612D00370A6B /* String+BASE64.swift in Sources */,
+				7CEBB8731D94612D00370A6B /* Router.swift in Sources */,
+				7CEBB8741D94612D00370A6B /* Swifter.swift in Sources */,
+				7C03242E1E51D20D00325E4F /* Misc.swift in Sources */,
+				7CEBB8771D94612D00370A6B /* Html.swift in Sources */,
+				7CEBB87B1D94612D00370A6B /* Base64.swift in Sources */,
 				7CEBB87C1D94612D00370A6B /* String+Misc.swift in Sources */,
 				7CEBB87D1D94612D00370A6B /* String+SHA1.swift in Sources */,
-				7CEBB87E1D94612D00370A6B /* WebSockets.swift in Sources */,
 				7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */,
+				7CAB37801E51E7D300FED085 /* WebSockets.swift in Sources */,
+				7C0324381E51D21600325E4F /* Tcp.swift in Sources */,
 				7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */,
+				7C0324391E51D21800325E4F /* Linux.swift in Sources */,
 				0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */,
 				0858E7F51D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
 				7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
+				7C0324331E51D21100325E4F /* Server.swift in Sources */,
+				7C03243E1E51D21C00325E4F /* MacOS.swift in Sources */,
+				7C03244B1E51D36900325E4F /* Error.swift in Sources */,
 				7CCD87851C660ED60068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -1117,6 +1135,7 @@
 					"DEBUG=1",
 					"$(inherited)",
 				);
+				INFOPLIST_FILE = "$(SRCROOT)/SwifterMac/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				MTL_ENABLE_DEBUG_INFO = YES;
@@ -1134,6 +1153,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
+				INFOPLIST_FILE = "$(SRCROOT)/SwifterMac/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				MTL_ENABLE_DEBUG_INFO = NO;

+ 35 - 12
XCode/SwifterSampleOSX/main.swift

@@ -7,22 +7,45 @@
 import Foundation
 import Swifter
 
-do {
-    let server = demoServer(try String.File.currentWorkingDirectory())
-    server["/testAfterBaseRoute"] = { request in
-        return .ok(.html("ok !"))
-    }
+let server = try Swifter()
+
+server.get("/") { _, request, responder in
+    responder(html {
+        "body" ~ {
+            "h1" ~ "Hello World !"
+        }
+    })
+}
+
+server.get("/stream") { _, request, responder in
+    responder(WebsocketResponse(request) { event in
+        switch event {
+            case .text(let value):
+                print("Got text message: \(value)")
+            case .binary(let value):
+                print("Got binary message: \(value)")
+            case .disconnected(_, _):
+                print("Peer disconneted")
+        }
+    })
+}
+
+server.get("/background") { _, _, closure in
     
     if #available(OSXApplicationExtension 10.10, *) {
-        try server.start(9080, forceIPv4: true)
+        DispatchQueue.global(qos: .background).async {
+            // Simulate http request to other service or a database query.
+            sleep(2)
+            closure(TextResponse(200, "Waited 2 secs for a response."))
+        }
     } else {
         // Fallback on earlier versions
     }
     
-    print("Server has started ( port = \(try server.port()) ). Try to connect now...")
-    
-    RunLoop.main.run()
-    
-} catch {
-    print("Server start error: \(error)")
 }
+
+while true {
+    try server.loop()
+}
+
+

+ 16 - 10
XCode/SwifterSampleiOS/ViewController.swift

@@ -9,21 +9,27 @@ import Swifter
 
 class ViewController: UIViewController {
     
-    private var server: HttpServer?
-    
     override func viewDidLoad() {
         super.viewDidLoad()
-        do {
-            let server = demoServer(Bundle.main.resourcePath!)
-            try server.start(9080)
-            self.server = server
-        } catch {
-            print("Server start error: \(error)")
+        
+        DispatchQueue.global(qos: .background).async {
+            do {
+                let server = try demoServer(Bundle.main.resourcePath!)
+                server.get("/") { params, request, responder in
+                    responder(html {
+                        "h1" ~ "Hello World !"
+                    })
+                }
+                while true {
+                    try server.loop()
+                }
+            } catch {
+                print("Server start error: \(error)")
+            }
         }
     }
     
     @IBAction func likedThis(sender: UIButton) {
-        self.server?.stop()
-        self.server = nil
+        
     }
 }