Răsfoiți Sursa

Removed HttpHandlers namespace.

Damian Kołakowski 10 ani în urmă
părinte
comite
00321c95be

+ 4 - 4
Sources/DemoServer.swift

@@ -13,10 +13,10 @@ public func demoServer(publicDir: String) -> HttpServer {
     
     let server = HttpServer()
     
-    server["/public/:path"] = HttpHandlers.shareFilesFromDirectory(publicDir)
-    server["/public/"] = HttpHandlers.shareFilesFromDirectory(publicDir)    // needed to serve index file at root level
+    server["/public/:path"] = shareFilesFromDirectory(publicDir)
+    server["/public/"] = shareFilesFromDirectory(publicDir)    // needed to serve index file at root level
 
-    server["/files/:path"] = HttpHandlers.directoryBrowser("/")
+    server["/files/:path"] = directoryBrowser("/")
 
     server["/"] = scopes {
         html {
@@ -180,7 +180,7 @@ public func demoServer(publicDir: String) -> HttpServer {
         })
     }
     
-    server["/websocket-echo"] = HttpHandlers.websocket({ (session, text) in
+    server["/websocket-echo"] = websocket({ (session, text) in
         session.writeText(text)
     }, { (session, binary) in
         session.writeBinary(binary)

+ 157 - 0
Sources/Files.swift

@@ -0,0 +1,157 @@
+//
+//  HttpHandlers+Files.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public func shareFilesFromDirectory(directoryPath: String) -> (HttpRequest -> HttpResponse) {
+    return { r in
+        guard let fileRelativePath = r.params.first else {
+            return .NotFound
+        }
+        let absolutePath = directoryPath + "/" + fileRelativePath.1
+        guard let file = try? File.openForReading(absolutePath) else {
+            return .NotFound
+        }
+        return .RAW(200, "OK", [:], { writer in
+            writer.write(file)
+            file.close()
+        })
+    }
+}
+
+private func fileNameToShare(directoryPath: String, request: HttpRequest) -> String? {
+    let path = request.path
+    let fileRelativePath = request.params.first
+
+    if !path.hasSuffix("/"), let fileRelativePath = fileRelativePath {
+        let absolutePath = directoryPath + "/" + fileRelativePath.1
+        return absolutePath
+    }
+
+    let fm = NSFileManager.defaultManager()
+    let possibleIndexFiles = ["index.html", "index.htm"] // add any other files you want to check for here
+    var folderPath = directoryPath
+    if let fileRelativePath = fileRelativePath {
+        folderPath += "/\(fileRelativePath.1)"
+    }
+
+    for indexFile in possibleIndexFiles {
+        let indexPath = "\(folderPath)/\(indexFile)"
+        if fm.fileExistsAtPath(indexPath) {
+            return indexPath
+        }
+    }
+    
+    return nil
+}
+
+let rangePrefix = "bytes="
+
+public func directory(dir: String) -> (HttpRequest -> HttpResponse) {
+    return { r in
+        
+        guard let localPath = r.params.first else {
+            return HttpResponse.NotFound
+        }
+        
+        let filesPath = dir + "/" + localPath.1
+        
+        guard let fileBody = NSData(contentsOfFile: filesPath) else {
+            return HttpResponse.NotFound
+        }
+        
+        if let rangeHeader = r.headers["range"] {
+            
+            guard rangeHeader.hasPrefix(rangePrefix) else {
+                return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
+            }
+            
+            #if os(Linux)
+                let rangeString = rangeHeader.substringFromIndex(HttpHandlers.rangePrefix.characters.count)
+            #else
+                let rangeString = rangeHeader.substringFromIndex(rangeHeader.startIndex.advancedBy(rangePrefix.characters.count))
+            #endif
+            
+            let rangeStringExploded = rangeString.split("-")
+            
+            guard rangeStringExploded.count == 2 else {
+                return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
+            }
+            
+            let startStr = rangeStringExploded[0]
+            let endStr   = rangeStringExploded[1]
+            
+            guard let start = Int(startStr), end = Int(endStr) else {
+                var array = [UInt8](count: fileBody.length, repeatedValue: 0)
+                fileBody.getBytes(&array, length: fileBody.length)
+                return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+            }
+            
+            let chunkLength = end - start
+            let chunkRange = NSRange(location: start, length: chunkLength + 1)
+            
+            guard chunkRange.location + chunkRange.length <= fileBody.length else {
+                return HttpResponse.RAW(416, "Requested range not satisfiable", nil, nil)
+            }
+            
+            let chunk = fileBody.subdataWithRange(chunkRange)
+            
+            let headers = [ "Content-Range" : "bytes \(startStr)-\(endStr)/\(fileBody.length)" ]
+            
+            var content = [UInt8](count: chunk.length, repeatedValue: 0)
+            chunk.getBytes(&content, length: chunk.length)
+            return HttpResponse.RAW(206, "Partial Content", headers, { $0.write(content) })
+        } else {
+            var content = [UInt8](count: fileBody.length, repeatedValue: 0)
+            fileBody.getBytes(&content, length: fileBody.length)
+            return HttpResponse.RAW(200, "OK", nil, { $0.write(content) })
+        }
+    }
+}
+
+public func directoryBrowser(dir: String) -> (HttpRequest -> HttpResponse) {
+    return { r in
+        guard let (_, value) = r.params.first else {
+            return HttpResponse.NotFound
+        }
+        let filePath = dir + "/" + value
+        do {
+            guard try File.exists(filePath) else {
+                return HttpResponse.NotFound
+            }
+            if try File.isDirectory(filePath) {
+                let files = try File.list(filePath)
+                return scopes {
+                    html {
+                        body {
+                            table(files) { file in
+                                tr {
+                                    td {
+                                        a {
+                                            href = r.path + "/" + file
+                                            inner = file
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }(r)
+            } else {
+                guard let file = try? File.openForReading(filePath) else {
+                    return .NotFound
+                }
+                return .RAW(200, "OK", [:], { writer in
+                    writer.write(file)
+                    file.close()
+                })
+            }
+        } catch {
+            return HttpResponse.InternalServerError
+        }
+    }
+}

+ 0 - 160
Sources/HttpHandlers+Files.swift

@@ -1,160 +0,0 @@
-//
-//  HttpHandlers+Files.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-extension HttpHandlers {
-    
-    public class func shareFilesFromDirectory(directoryPath: String) -> (HttpRequest -> HttpResponse) {
-        return { r in
-            guard let fileRelativePath = r.params.first else {
-                return .NotFound
-            }
-            let absolutePath = directoryPath + "/" + fileRelativePath.1
-            guard let file = try? File.openForReading(absolutePath) else {
-                return .NotFound
-            }
-            return .RAW(200, "OK", [:], { writer in
-                writer.write(file)
-                file.close()
-            })
-        }
-    }
-
-    private class func fileNameToShare(directoryPath: String, request: HttpRequest) -> String? {
-        let path = request.path
-        let fileRelativePath = request.params.first
-
-        if !path.hasSuffix("/"), let fileRelativePath = fileRelativePath {
-            let absolutePath = directoryPath + "/" + fileRelativePath.1
-            return absolutePath
-        }
-
-        let fm = NSFileManager.defaultManager()
-        let possibleIndexFiles = ["index.html", "index.htm"] // add any other files you want to check for here
-        var folderPath = directoryPath
-        if let fileRelativePath = fileRelativePath {
-            folderPath += "/\(fileRelativePath.1)"
-        }
-
-        for indexFile in possibleIndexFiles {
-            let indexPath = "\(folderPath)/\(indexFile)"
-            if fm.fileExistsAtPath(indexPath) {
-                return indexPath
-            }
-        }
-        
-        return nil
-    }
-
-    private static let rangePrefix = "bytes="
-    
-    public class func directory(dir: String) -> (HttpRequest -> HttpResponse) {
-        return { r in
-            
-            guard let localPath = r.params.first else {
-                return HttpResponse.NotFound
-            }
-            
-            let filesPath = dir + "/" + localPath.1
-            
-            guard let fileBody = NSData(contentsOfFile: filesPath) else {
-                return HttpResponse.NotFound
-            }
-            
-            if let rangeHeader = r.headers["range"] {
-                
-                guard rangeHeader.hasPrefix(HttpHandlers.rangePrefix) else {
-                    return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
-                }
-                
-                #if os(Linux)
-                    let rangeString = rangeHeader.substringFromIndex(HttpHandlers.rangePrefix.characters.count)
-                #else
-                    let rangeString = rangeHeader.substringFromIndex(rangeHeader.startIndex.advancedBy(HttpHandlers.rangePrefix.characters.count))
-                #endif
-                
-                let rangeStringExploded = rangeString.split("-")
-                
-                guard rangeStringExploded.count == 2 else {
-                    return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
-                }
-                
-                let startStr = rangeStringExploded[0]
-                let endStr   = rangeStringExploded[1]
-                
-                guard let start = Int(startStr), end = Int(endStr) else {
-                    var array = [UInt8](count: fileBody.length, repeatedValue: 0)
-                    fileBody.getBytes(&array, length: fileBody.length)
-                    return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
-                }
-                
-                let chunkLength = end - start
-                let chunkRange = NSRange(location: start, length: chunkLength + 1)
-                
-                guard chunkRange.location + chunkRange.length <= fileBody.length else {
-                    return HttpResponse.RAW(416, "Requested range not satisfiable", nil, nil)
-                }
-                
-                let chunk = fileBody.subdataWithRange(chunkRange)
-                
-                let headers = [ "Content-Range" : "bytes \(startStr)-\(endStr)/\(fileBody.length)" ]
-                
-                var content = [UInt8](count: chunk.length, repeatedValue: 0)
-                chunk.getBytes(&content, length: chunk.length)
-                return HttpResponse.RAW(206, "Partial Content", headers, { $0.write(content) })
-            } else {
-                var content = [UInt8](count: fileBody.length, repeatedValue: 0)
-                fileBody.getBytes(&content, length: fileBody.length)
-                return HttpResponse.RAW(200, "OK", nil, { $0.write(content) })
-            }
-        }
-    }
-    
-    public class func directoryBrowser(dir: String) -> (HttpRequest -> HttpResponse) {
-        return { r in
-            guard let (_, value) = r.params.first else {
-                return HttpResponse.NotFound
-            }
-            let filePath = dir + "/" + value
-            do {
-                guard try File.exists(filePath) else {
-                    return HttpResponse.NotFound
-                }
-                if try File.isDirectory(filePath) {
-                    let files = try File.list(filePath)
-                    return scopes {
-                        html {
-                            body {
-                                table(files) { file in
-                                    tr {
-                                        td {
-                                            a {
-                                                href = r.path + "/" + file
-                                                inner = file
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }(r)
-                } else {
-                    guard let file = try? File.openForReading(filePath) else {
-                        return .NotFound
-                    }
-                    return .RAW(200, "OK", [:], { writer in
-                        writer.write(file)
-                        file.close()
-                    })
-                }
-            } catch {
-                return HttpResponse.InternalServerError
-            }
-        }
-    }
-}

+ 0 - 171
Sources/HttpHandlers+WebSockets.swift

@@ -1,171 +0,0 @@
-//
-//  HttpHandlers+WebSockets.swift
-//  Swifter
-//
-//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-extension HttpHandlers {
-    
-    public class func websocket(
-            text: ((WebSocketSession, String) -> Void)?,
-        _ binary: ((WebSocketSession, [UInt8]) -> Void)?) -> (HttpRequest -> HttpResponse) {
-        return { r in
-            guard r.headers["upgrade"] == "websocket" else {
-                return .BadRequest(.Text("Invalid value of 'Upgrade' header: \(r.headers["upgrade"])"))
-            }
-            guard r.headers["connection"] == "Upgrade" else {
-                return .BadRequest(.Text("Invalid value of 'Connection' header: \(r.headers["connection"])"))
-            }
-            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)
-                while let frame = try? session.readFrame() {
-                    switch frame.opcode {
-                    case .Text:
-                        if let handleText = text {
-                            handleText(session, String.fromUInt8(frame.payload))
-                        }
-                    case .Binary:
-                        if let handleBinary = binary {
-                            handleBinary(session, frame.payload)
-                        }
-                    default: break
-                    }
-                }
-            }
-            let secWebSocketAccept = String.toBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1())
-            let headers = [ "Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept]
-            return HttpResponse.SwitchProtocols(headers, protocolSessionClosure)
-        }
-    }
-    
-    public class WebSocketSession {
-        
-        public enum Error: ErrorType { case UnknownOpCode(String), UnMaskedFrame }
-        public enum OpCode { case Continue, Close, Ping, Pong, Text, Binary }
-        
-        public class Frame {
-            public var opcode = OpCode.Close
-            public var fin = false
-            public var payload = [UInt8]()
-        }
-
-        private let socket: Socket
-        
-        public init(_ socket: Socket) {
-            self.socket = socket
-        }
-        
-        public func writeText(text: String) -> Void {
-            self.writeFrame(ArraySlice(text.utf8), OpCode.Text)
-        }
-    
-        public func writeBinary(binary: [UInt8]) -> Void {
-            self.writeBinary(ArraySlice(binary))
-        }
-        
-        public func writeBinary(binary: ArraySlice<UInt8>) -> Void {
-            self.writeFrame(binary, OpCode.Binary)
-        }
-        
-        private func writeFrame(data: ArraySlice<UInt8>, _ op: OpCode, _ fin: Bool = true) {
-            let finAndOpCode = encodeFinAndOpCode(fin, op: op)
-            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)
-            }
-        }
-        
-        private func encodeFinAndOpCode(fin: Bool, op: OpCode) -> UInt8 {
-            var encodedByte = UInt8(fin ? 0x80 : 0x00);
-            switch op {
-            case .Continue : encodedByte |= 0x00 & 0x0F;
-            case .Text     : encodedByte |= 0x01 & 0x0F;
-            case .Binary   : encodedByte |= 0x02 & 0x0F;
-            case .Close    : encodedByte |= 0x08 & 0x0F;
-            case .Ping     : encodedByte |= 0x09 & 0x0F;
-            case .Pong     : encodedByte |= 0x0A & 0x0F;
-            }
-            return encodedByte
-        }
-        
-        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));
-                encodedBytes.append(UInt8(len & 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 func readFrame() throws -> Frame {
-            let frm = Frame()
-            let fst = try socket.read()
-            frm.fin = fst & 0x80 != 0
-            let opc = fst & 0x0F
-            switch opc {
-                case 0x00: frm.opcode = OpCode.Continue
-                case 0x01: frm.opcode = OpCode.Text
-                case 0x02: frm.opcode = OpCode.Binary
-                case 0x08: frm.opcode = OpCode.Close
-                case 0x09: frm.opcode = OpCode.Ping
-                case 0x0A: frm.opcode = OpCode.Pong
-                // "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 )
-                default  : throw Error.UnknownOpCode("\(opc)")
-            }
-            let sec = try socket.read()
-            let msk = sec & 0x80 != 0
-            guard msk else {
-                // "...a client MUST mask all frames that it sends to the serve.."
-                // http://tools.ietf.org/html/rfc6455#section-5.1
-                throw Error.UnMaskedFrame
-            }
-            var len = UInt64(sec & 0x7F)
-            if len == 0x7E {
-                let b0 = UInt64(try socket.read())
-                let b1 = UInt64(try socket.read())
-                len = UInt64(littleEndian: b0 << 8 | b1)
-            } 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())
-                len = UInt64(littleEndian: b0 << 54 | b1 << 48 | b2 << 40 | b3 << 32 | b4 << 24 | b5 << 16 | b6 << 8 | b7)
-            }
-            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
-        }
-    }
-}

+ 0 - 12
Sources/HttpHandlers.swift

@@ -1,12 +0,0 @@
-//
-//  Handlers.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-public class HttpHandlers {
-
-}

+ 0 - 0
Sources/HttpHandlers+Scopes.swift → Sources/Scopes.swift


+ 168 - 0
Sources/WebSockets.swift

@@ -0,0 +1,168 @@
+//
+//  HttpHandlers+WebSockets.swift
+//  Swifter
+//
+//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public func websocket(
+        text: ((WebSocketSession, String) -> Void)?,
+    _ binary: ((WebSocketSession, [UInt8]) -> Void)?) -> (HttpRequest -> HttpResponse) {
+    return { r in
+        guard r.headers["upgrade"] == "websocket" else {
+            return .BadRequest(.Text("Invalid value of 'Upgrade' header: \(r.headers["upgrade"])"))
+        }
+        guard r.headers["connection"] == "Upgrade" else {
+            return .BadRequest(.Text("Invalid value of 'Connection' header: \(r.headers["connection"])"))
+        }
+        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)
+            while let frame = try? session.readFrame() {
+                switch frame.opcode {
+                case .Text:
+                    if let handleText = text {
+                        handleText(session, String.fromUInt8(frame.payload))
+                    }
+                case .Binary:
+                    if let handleBinary = binary {
+                        handleBinary(session, frame.payload)
+                    }
+                default: break
+                }
+            }
+        }
+        let secWebSocketAccept = String.toBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1())
+        let headers = [ "Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept]
+        return HttpResponse.SwitchProtocols(headers, protocolSessionClosure)
+    }
+}
+
+public class WebSocketSession {
+    
+    public enum Error: ErrorType { case UnknownOpCode(String), UnMaskedFrame }
+    public enum OpCode { case Continue, Close, Ping, Pong, Text, Binary }
+    
+    public class Frame {
+        public var opcode = OpCode.Close
+        public var fin = false
+        public var payload = [UInt8]()
+    }
+
+    private let socket: Socket
+    
+    public init(_ socket: Socket) {
+        self.socket = socket
+    }
+    
+    public func writeText(text: String) -> Void {
+        self.writeFrame(ArraySlice(text.utf8), OpCode.Text)
+    }
+
+    public func writeBinary(binary: [UInt8]) -> Void {
+        self.writeBinary(ArraySlice(binary))
+    }
+    
+    public func writeBinary(binary: ArraySlice<UInt8>) -> Void {
+        self.writeFrame(binary, OpCode.Binary)
+    }
+    
+    private func writeFrame(data: ArraySlice<UInt8>, _ op: OpCode, _ fin: Bool = true) {
+        let finAndOpCode = encodeFinAndOpCode(fin, op: op)
+        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)
+        }
+    }
+    
+    private func encodeFinAndOpCode(fin: Bool, op: OpCode) -> UInt8 {
+        var encodedByte = UInt8(fin ? 0x80 : 0x00);
+        switch op {
+        case .Continue : encodedByte |= 0x00 & 0x0F;
+        case .Text     : encodedByte |= 0x01 & 0x0F;
+        case .Binary   : encodedByte |= 0x02 & 0x0F;
+        case .Close    : encodedByte |= 0x08 & 0x0F;
+        case .Ping     : encodedByte |= 0x09 & 0x0F;
+        case .Pong     : encodedByte |= 0x0A & 0x0F;
+        }
+        return encodedByte
+    }
+    
+    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));
+            encodedBytes.append(UInt8(len & 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 func readFrame() throws -> Frame {
+        let frm = Frame()
+        let fst = try socket.read()
+        frm.fin = fst & 0x80 != 0
+        let opc = fst & 0x0F
+        switch opc {
+            case 0x00: frm.opcode = OpCode.Continue
+            case 0x01: frm.opcode = OpCode.Text
+            case 0x02: frm.opcode = OpCode.Binary
+            case 0x08: frm.opcode = OpCode.Close
+            case 0x09: frm.opcode = OpCode.Ping
+            case 0x0A: frm.opcode = OpCode.Pong
+            // "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 )
+            default  : throw Error.UnknownOpCode("\(opc)")
+        }
+        let sec = try socket.read()
+        let msk = sec & 0x80 != 0
+        guard msk else {
+            // "...a client MUST mask all frames that it sends to the serve.."
+            // http://tools.ietf.org/html/rfc6455#section-5.1
+            throw Error.UnMaskedFrame
+        }
+        var len = UInt64(sec & 0x7F)
+        if len == 0x7E {
+            let b0 = UInt64(try socket.read())
+            let b1 = UInt64(try socket.read())
+            len = UInt64(littleEndian: b0 << 8 | b1)
+        } 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())
+            len = UInt64(littleEndian: b0 << 54 | b1 << 48 | b2 << 40 | b3 << 32 | b4 << 24 | b5 << 16 | b6 << 8 | b7)
+        }
+        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
+    }
+}

+ 22 - 28
Swifter.xcodeproj/project.pbxproj

@@ -21,7 +21,6 @@
 		7C73C6911C2615FE00AEF6CA /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E610A51BD6397D00B7D17A /* SwiftyJSON.swift */; };
 		7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */; };
 		7C73C6AA1C261A2100AEF6CA /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6941C2619E100AEF6CA /* DemoServer.swift */; };
-		7C73C6AB1C261A2100AEF6CA /* HttpHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */; };
 		7C73C6AC1C261A2100AEF6CA /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6961C2619E100AEF6CA /* HttpParser.swift */; };
 		7C73C6AD1C261A2100AEF6CA /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6971C2619E100AEF6CA /* HttpRequest.swift */; };
 		7C73C6AE1C261A2100AEF6CA /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6981C2619E100AEF6CA /* HttpResponse.swift */; };
@@ -31,7 +30,6 @@
 		7C73C6B21C261A2100AEF6CA /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69C1C2619E100AEF6CA /* Socket.swift */; };
 		7C73C6B31C261A2100AEF6CA /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69D1C2619E100AEF6CA /* String+Misc.swift */; };
 		7C73C6B51C261A2600AEF6CA /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6941C2619E100AEF6CA /* DemoServer.swift */; };
-		7C73C6B61C261A2600AEF6CA /* HttpHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */; };
 		7C73C6B71C261A2600AEF6CA /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6961C2619E100AEF6CA /* HttpParser.swift */; };
 		7C73C6B81C261A2600AEF6CA /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6971C2619E100AEF6CA /* HttpRequest.swift */; };
 		7C73C6B91C261A2600AEF6CA /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6981C2619E100AEF6CA /* HttpResponse.swift */; };
@@ -40,20 +38,20 @@
 		7C73C6BC1C261A2600AEF6CA /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */; };
 		7C73C6BD1C261A2600AEF6CA /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69C1C2619E100AEF6CA /* Socket.swift */; };
 		7C73C6BE1C261A2600AEF6CA /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69D1C2619E100AEF6CA /* String+Misc.swift */; };
-		7C76B6DE1D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */; };
-		7C76B6DF1D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */; };
-		7C76B6E01D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */; };
-		7C76B6E11D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */; };
+		7C76B6DE1D2BB1050030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* Scopes.swift */; };
+		7C76B6DF1D2BB1050030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* Scopes.swift */; };
+		7C76B6E01D2BB1050030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* Scopes.swift */; };
+		7C76B6E11D2BB1050030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6DD1D2BB1050030FC98 /* Scopes.swift */; };
 		7C76B6E31D2BB1320030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6E21D2BB1320030FC98 /* Process.swift */; };
 		7C76B6E41D2BB1320030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6E21D2BB1320030FC98 /* Process.swift */; };
 		7C76B6E51D2BB1320030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6E21D2BB1320030FC98 /* Process.swift */; };
 		7C76B6E61D2BB1320030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6E21D2BB1320030FC98 /* Process.swift */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
 		7CB102E01A17381D00CBA3B4 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
-		7CC0F8C91C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */; };
-		7CC0F8CA1C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */; };
-		7CC0F8CC1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */; };
-		7CC0F8CD1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */; };
+		7CC0F8C91C50136B00B65A94 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* Files.swift */; };
+		7CC0F8CA1C50136B00B65A94 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* Files.swift */; };
+		7CC0F8CC1C5014A200B65A94 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* WebSockets.swift */; };
+		7CC0F8CD1C5014A200B65A94 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* WebSockets.swift */; };
 		7CCD87611C66099B0068099B /* Swifter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AE893E71C05127900A29F63 /* Swifter.framework */; };
 		7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */; };
 		7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */; };
@@ -124,7 +122,6 @@
 		7C2BEC771C518B7C00B8EE90 /* String+SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SHA1.swift"; sourceTree = "<group>"; };
 		7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsWebSocketSession.swift; sourceTree = "<group>"; };
 		7C73C6941C2619E100AEF6CA /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = "<group>"; };
-		7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpHandlers.swift; sourceTree = "<group>"; };
 		7C73C6961C2619E100AEF6CA /* HttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpParser.swift; sourceTree = "<group>"; };
 		7C73C6971C2619E100AEF6CA /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
 		7C73C6981C2619E100AEF6CA /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = "<group>"; };
@@ -133,14 +130,14 @@
 		7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerIO.swift; sourceTree = "<group>"; };
 		7C73C69C1C2619E100AEF6CA /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = "<group>"; };
 		7C73C69D1C2619E100AEF6CA /* String+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = "<group>"; };
-		7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HttpHandlers+Scopes.swift"; sourceTree = "<group>"; };
+		7C76B6DD1D2BB1050030FC98 /* Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scopes.swift; sourceTree = "<group>"; };
 		7C76B6E21D2BB1320030FC98 /* Process.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Process.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>"; };
 		7CB102DF1A17381D00CBA3B4 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
-		7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HttpHandlers+Files.swift"; sourceTree = "<group>"; };
-		7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HttpHandlers+WebSockets.swift"; sourceTree = "<group>"; };
+		7CC0F8C81C50136B00B65A94 /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = "<group>"; };
+		7CC0F8CB1C5014A200B65A94 /* WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSockets.swift; sourceTree = "<group>"; };
 		7CCD875C1C66099B0068099B /* SwifteriOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifteriOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CCD87601C66099B0068099B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsHttpParser.swift; sourceTree = "<group>"; };
@@ -312,10 +309,9 @@
 			isa = PBXGroup;
 			children = (
 				7C73C6941C2619E100AEF6CA /* DemoServer.swift */,
-				7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */,
-				7C76B6DD1D2BB1050030FC98 /* HttpHandlers+Scopes.swift */,
-				7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */,
-				7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */,
+				7CC0F8C81C50136B00B65A94 /* Files.swift */,
+				7C76B6DD1D2BB1050030FC98 /* Scopes.swift */,
+				7CC0F8CB1C5014A200B65A94 /* WebSockets.swift */,
 				7C73C6961C2619E100AEF6CA /* HttpParser.swift */,
 				7C73C6971C2619E100AEF6CA /* HttpRequest.swift */,
 				7C73C6981C2619E100AEF6CA /* HttpResponse.swift */,
@@ -564,20 +560,19 @@
 			files = (
 				7C2BEC791C5195EE00B8EE90 /* String+SHA1.swift in Sources */,
 				7C73C6AA1C261A2100AEF6CA /* DemoServer.swift in Sources */,
-				7C73C6AB1C261A2100AEF6CA /* HttpHandlers.swift in Sources */,
 				7C1A2BFB1C5605F50026D3BF /* String+BASE64.swift in Sources */,
 				7C73C6AC1C261A2100AEF6CA /* HttpParser.swift in Sources */,
 				7C73C6AD1C261A2100AEF6CA /* HttpRequest.swift in Sources */,
 				7C73C6AE1C261A2100AEF6CA /* HttpResponse.swift in Sources */,
 				7C76B6E31D2BB1320030FC98 /* Process.swift in Sources */,
 				7C73C6AF1C261A2100AEF6CA /* HttpRouter.swift in Sources */,
-				7CC0F8CC1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */,
+				7CC0F8CC1C5014A200B65A94 /* WebSockets.swift in Sources */,
 				7C73C6B01C261A2100AEF6CA /* HttpServer.swift in Sources */,
 				7CCD87871C676EE50068099B /* File.swift in Sources */,
 				7C73C6B11C261A2100AEF6CA /* HttpServerIO.swift in Sources */,
 				7C73C6B21C261A2100AEF6CA /* Socket.swift in Sources */,
-				7C76B6DE1D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */,
-				7CC0F8C91C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */,
+				7C76B6DE1D2BB1050030FC98 /* Scopes.swift in Sources */,
+				7CC0F8C91C50136B00B65A94 /* Files.swift in Sources */,
 				7C73C6B31C261A2100AEF6CA /* String+Misc.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -588,20 +583,19 @@
 			files = (
 				7C2BEC7A1C5195F200B8EE90 /* String+SHA1.swift in Sources */,
 				7C73C6B51C261A2600AEF6CA /* DemoServer.swift in Sources */,
-				7C73C6B61C261A2600AEF6CA /* HttpHandlers.swift in Sources */,
 				7C1A2BFC1C5605F50026D3BF /* String+BASE64.swift in Sources */,
 				7C73C6B71C261A2600AEF6CA /* HttpParser.swift in Sources */,
 				7C73C6B81C261A2600AEF6CA /* HttpRequest.swift in Sources */,
 				7C73C6B91C261A2600AEF6CA /* HttpResponse.swift in Sources */,
 				7C76B6E41D2BB1320030FC98 /* Process.swift in Sources */,
 				7C73C6BA1C261A2600AEF6CA /* HttpRouter.swift in Sources */,
-				7CC0F8CD1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */,
+				7CC0F8CD1C5014A200B65A94 /* WebSockets.swift in Sources */,
 				7C73C6BB1C261A2600AEF6CA /* HttpServer.swift in Sources */,
 				7CCD87881C676EE50068099B /* File.swift in Sources */,
 				7C73C6BC1C261A2600AEF6CA /* HttpServerIO.swift in Sources */,
 				7C73C6BD1C261A2600AEF6CA /* Socket.swift in Sources */,
-				7C76B6DF1D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */,
-				7CC0F8CA1C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */,
+				7C76B6DF1D2BB1050030FC98 /* Scopes.swift in Sources */,
+				7CC0F8CA1C50136B00B65A94 /* Files.swift in Sources */,
 				7C73C6BE1C261A2600AEF6CA /* String+Misc.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -631,7 +625,7 @@
 			files = (
 				7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */,
 				7C76B6E51D2BB1320030FC98 /* Process.swift in Sources */,
-				7C76B6E01D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */,
+				7C76B6E01D2BB1050030FC98 /* Scopes.swift in Sources */,
 				7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);
@@ -643,7 +637,7 @@
 			files = (
 				7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */,
 				7C76B6E61D2BB1320030FC98 /* Process.swift in Sources */,
-				7C76B6E11D2BB1050030FC98 /* HttpHandlers+Scopes.swift in Sources */,
+				7C76B6E11D2BB1050030FC98 /* Scopes.swift in Sources */,
 				7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87851C660ED60068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);