Explorar el Código

Added parser for WebSocket frame.

Damian Kołakowski hace 10 años
padre
commit
623089da40

+ 66 - 10
Sources/HttpHandlers+WebSockets.swift

@@ -9,25 +9,81 @@ import Foundation
 
 extension HttpHandlers {
     
-    public class func websocket(message:(String) -> ()) -> (HttpRequest -> HttpResponse) {
-        func closure(r: HttpRequest) -> HttpResponse {
+    public class func websocket(handle:(String) -> ()) -> (HttpRequest -> HttpResponse) {
+        return { r in
             guard r.headers["upgrade"] == "websocket" else {
                 return .BadRequest
             }
+            
             guard r.headers["connection"] == "Upgrade" else {
                 return .BadRequest
             }
             guard let secWebSocketKey = r.headers["sec-websocket-key"] else {
                 return .BadRequest
             }
-            let accept = (secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1()
-            let acceptBASE64 = String(data: (accept.dataUsingEncoding(NSUTF8StringEncoding)?.base64EncodedDataWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength))!, encoding: NSUTF8StringEncoding)!;
-            let upgradeHeaders = [ "Upgrade": "weboscket", "Connection": "Upgrade",
-                "Sec-WebSocket-Accept": acceptBASE64
-            ]
-            return HttpResponse.RAW(101, "Switching Protocols", upgradeHeaders, nil)
+            let accept = String.encodeToBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1())
+            let upgradeHeaders = [ "Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": accept]
+            return HttpResponse.SwitchProtocols(upgradeHeaders) { socket  in
+                let parser = WebSocketParser()
+                while let frame = try? parser.readFrame(socket) {
+                    handle(String.fromUInt8(frame.payload))
+                }
+            }
+        }
+    }
+    
+    public class WebSocketParser {
+        
+        enum Error: ErrorType {
+            case UnknownOpCode(String)
+        }
+        
+        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]()
+        }
+        
+        public func readFrame(socket: Socket) 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
+                default  : throw Error.UnknownOpCode("\(opc)")
+            }
+            let sec = try socket.read()
+            let msk = sec & 0x0F != 0
+            var len = UInt64(sec & 0x7F)
+            if len == 0x7E {1
+                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 = msk ? [try socket.read(), try socket.read(), try socket.read(), try socket.read()] : []
+            for i in 0..<len {
+                let byte = try socket.read()
+                frm.payload.append(msk ? byte ^ mask[Int(i % 4)] : byte)
+            }
+            return frm
         }
-        return closure
     }
-
 }

+ 1 - 3
Sources/HttpHandlers.swift

@@ -7,6 +7,4 @@
 
 import Foundation
 
-public class HttpHandlers {
-
-}
+public class HttpHandlers { }

+ 16 - 5
Sources/HttpResponse.swift

@@ -64,7 +64,7 @@ public enum HttpResponseBody {
 
 public enum HttpResponse {
     
-    case SwitchProtocols
+    case SwitchProtocols([String: String], Socket -> Void)
     case OK(HttpResponseBody), Created, Accepted
     case MovedPermanently(String)
     case BadRequest, Unauthorized, Forbidden, NotFound
@@ -73,12 +73,12 @@ public enum HttpResponse {
     
     func statusCode() -> Int {
         switch self {
-        case .SwitchProtocols         : return 101
+        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 .BadRequest(_)           : return 400
         case .Unauthorized            : return 401
         case .Forbidden               : return 403
         case .NotFound                : return 404
@@ -89,12 +89,12 @@ public enum HttpResponse {
     
     func reasonPhrase() -> String {
         switch self {
-        case .SwitchProtocols          : return "Switching Protocols"
+        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 .BadRequest(_)            : return "Bad Request"
         case .Unauthorized             : return "Unauthorized"
         case .Forbidden                : return "Forbidden"
         case .NotFound                 : return "Not Found"
@@ -106,6 +106,10 @@ public enum HttpResponse {
     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"
@@ -132,6 +136,13 @@ public enum HttpResponse {
         default                        : return (-1, nil)
         }
     }
+    
+    func protocolHandler() -> (Socket -> Void)?  {
+        switch self {
+        case SwitchProtocols(_, let handler) : return handler
+        default: return nil
+        }
+    }
 }
 
 /**

+ 4 - 0
Sources/HttpServerIO.swift

@@ -53,6 +53,10 @@ public class HttpServerIO {
                 print("Failed to send response: \(error)")
                 break
             }
+            if let handler = response.protocolHandler() {
+                handler(socket)
+                break
+            }
             if !keepConnection { break }
         }
         socket.release()

+ 19 - 18
Sources/String+SHA1.swift

@@ -10,6 +10,10 @@ import Foundation
 extension String {
     
     public func SHA1() -> String {
+        return SHA1().reduce("") { $0 + String(format: "%02x", $1) }
+    }
+    
+    public func SHA1() -> [UInt8] {
         
         // Alghorithm from: https://en.wikipedia.org/wiki/SHA-1
         
@@ -33,9 +37,7 @@ extension String {
         
         let padBytesCount = ( message.count + 8 ) % 64
         
-        for _ in padBytesCount...63 {
-            message.append(0x00)
-        }
+        for _ in padBytesCount...63 { message.append(0x00) }
         
         // append ml, in a 64-bit big-endian integer. Thus, the total length is a multiple of 512 bits.
         
@@ -76,20 +78,19 @@ extension String {
                 var f = UInt32(0)
                 var k = UInt32(0)
                 switch i {
-                case 0...19:
-                    f = (b & c) | ((~b) & d)
-                    k = 0x5A827999
-                case 20...39:
-                    f = b ^ c ^ d
-                    k = 0x6ED9EBA1
-                case 40...59:
-                    f = (b & c) | (b & d) | (c & d)
-                    k = 0x8F1BBCDC
-                case 60...79:
-                    f = b ^ c ^ d
-                    k = 0xCA62C1D6
-                default:
-                    print("")
+                    case 0...19:
+                        f = (b & c) | ((~b) & d)
+                        k = 0x5A827999
+                    case 20...39:
+                        f = b ^ c ^ d
+                        k = 0x6ED9EBA1
+                    case 40...59:
+                        f = (b & c) | (b & d) | (c & d)
+                        k = 0x8F1BBCDC
+                    case 60...79:
+                        f = b ^ c ^ d
+                        k = 0xCA62C1D6
+                    default: break
                 }
                 let temp = (rotateLeft(a, 5) &+ f &+ e &+ k &+ words[i]) & 0xFFFFFFFF
                 e = d
@@ -124,7 +125,7 @@ extension String {
         result += ([UInt8(h3Big & 0xFF), UInt8((h3Big >> 8) & 0xFF), UInt8((h3Big >> 16) & 0xFF), UInt8((h3Big >> 24) & 0xFF)]);
         result += ([UInt8(h4Big & 0xff), UInt8((h4Big >> 8) & 0xFF), UInt8((h4Big >> 16) & 0xFF), UInt8((h4Big >> 24) & 0xFF)]);
 
-        return result.reduce("") { $0 + String(format: "%02x", $1) }
+        return result;
     }
     
     func rotateLeft(v: UInt32, _ n: UInt32) -> UInt32 {

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


+ 63 - 31
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -1589,11 +1589,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpServerIO.swift"
-            timestampString = "475011147.040336"
+            timestampString = "475787248.651869"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "93"
-            endingLineNumber = "93"
+            startingLineNumber = "97"
+            endingLineNumber = "97"
             landmarkName = "respond(_:response:keepAlive:)"
             landmarkType = "5">
          </BreakpointContent>
@@ -1653,7 +1653,7 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "SwifterSampleOSX/main.swift"
-            timestampString = "475409598.101265"
+            timestampString = "475797519.716601"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
             startingLineNumber = "20"
@@ -1699,11 +1699,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475358690.499811"
+            timestampString = "475797167.424719"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "36"
-            endingLineNumber = "36"
+            startingLineNumber = "93"
+            endingLineNumber = "93"
             landmarkName = "HttpHandlers"
             landmarkType = "3">
             <Locations>
@@ -1747,11 +1747,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475358690.499811"
+            timestampString = "475797167.424719"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "42"
-            endingLineNumber = "42"
+            startingLineNumber = "99"
+            endingLineNumber = "99"
             landmarkName = "HttpHandlers"
             landmarkType = "3">
          </BreakpointContent>
@@ -1763,11 +1763,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475358690.499811"
+            timestampString = "475797167.424719"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "35"
-            endingLineNumber = "35"
+            startingLineNumber = "92"
+            endingLineNumber = "92"
             landmarkName = "HttpHandlers"
             landmarkType = "3">
          </BreakpointContent>
@@ -1939,11 +1939,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475358690.499811"
+            timestampString = "475797167.424719"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "34"
-            endingLineNumber = "34"
+            startingLineNumber = "91"
+            endingLineNumber = "91"
             landmarkName = "HttpHandlers"
             landmarkType = "3">
          </BreakpointContent>
@@ -2193,7 +2193,7 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "SwifterSampleOSX/main.swift"
-            timestampString = "475409598.101265"
+            timestampString = "475797519.716601"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
             startingLineNumber = "13"
@@ -2299,17 +2299,17 @@
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
-            shouldBeEnabled = "Yes"
+            shouldBeEnabled = "No"
             ignoreCount = "0"
             continueAfterRunningActions = "No"
-            filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475358683.662665"
+            filePath = "Sources/String+BASE64.swift"
+            timestampString = "475409858.054275"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "28"
-            endingLineNumber = "28"
-            landmarkName = "closure(_:)"
-            landmarkType = "7">
+            startingLineNumber = "16"
+            endingLineNumber = "16"
+            landmarkName = "encodeToBase64(_:)"
+            landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -2322,8 +2322,8 @@
             timestampString = "475409858.054275"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "16"
-            endingLineNumber = "16"
+            startingLineNumber = "40"
+            endingLineNumber = "40"
             landmarkName = "encodeToBase64(_:)"
             landmarkType = "5">
          </BreakpointContent>
@@ -2334,13 +2334,45 @@
             shouldBeEnabled = "No"
             ignoreCount = "0"
             continueAfterRunningActions = "No"
-            filePath = "Sources/String+BASE64.swift"
-            timestampString = "475409858.054275"
+            filePath = "Sources/HttpHandlers+WebSockets.swift"
+            timestampString = "475797519.716601"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "40"
-            endingLineNumber = "40"
-            landmarkName = "encodeToBase64(_:)"
+            startingLineNumber = "27"
+            endingLineNumber = "27"
+            landmarkName = "websocket(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpHandlers+WebSockets.swift"
+            timestampString = "475797167.424719"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "13"
+            endingLineNumber = "13"
+            landmarkName = "websocket(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/HttpServerIO.swift"
+            timestampString = "475787353.967887"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "49"
+            endingLineNumber = "49"
+            landmarkName = "handleConnection(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>