Преглед изворни кода

WebSocketSesion accepts ArraySlice type.
Fixed the decoding of frame masking flag.
Added tests for WebSocekt frames parser.

Damian Kołakowski пре 10 година
родитељ
комит
f1dbeafc75

+ 8 - 4
Sources/HttpHandlers+WebSockets.swift

@@ -57,19 +57,23 @@ extension HttpHandlers {
 
         private let socket: Socket
         
-        init(_ socket: Socket) {
+        public init(_ socket: Socket) {
             self.socket = socket
         }
         
         public func writeText(text: String) -> Void {
-            self.writeFrame([UInt8](text.utf8), OpCode.Text)
+            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: [UInt8], _ op: OpCode, _ fin: Bool = true) {
+        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 {
@@ -135,7 +139,7 @@ extension HttpHandlers {
                 default  : throw Error.UnknownOpCode("\(opc)")
             }
             let sec = try socket.read()
-            let msk = sec & 0x0F != 0
+            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

+ 16 - 1
Sources/HttpHandlers.swift

@@ -7,4 +7,19 @@
 
 import Foundation
 
-public class HttpHandlers { }
+public class HttpHandlers {
+
+    public enum HtmlNode {
+        case body
+        case head
+
+    }
+    
+
+    
+    
+//    public class func html(directoryPath: String) -> (HttpRequest -> HttpResponse) {
+//        
+//        
+//    }
+}

+ 6 - 0
Swifter.xcodeproj/project.pbxproj

@@ -15,6 +15,8 @@
 		7C2BEC781C518B7C00B8EE90 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2BEC771C518B7C00B8EE90 /* String+SHA1.swift */; };
 		7C2BEC791C5195EE00B8EE90 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2BEC771C518B7C00B8EE90 /* String+SHA1.swift */; };
 		7C2BEC7A1C5195F200B8EE90 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2BEC771C518B7C00B8EE90 /* String+SHA1.swift */; };
+		7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
+		7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
 		7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7C73C6911C2615FE00AEF6CA /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E610A51BD6397D00B7D17A /* SwiftyJSON.swift */; };
@@ -121,6 +123,7 @@
 		7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
 		7C1A2BFA1C5605F50026D3BF /* String+BASE64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BASE64.swift"; sourceTree = "<group>"; };
 		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>"; };
@@ -284,6 +287,7 @@
 			children = (
 				7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */,
 				7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */,
+				7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */,
 			);
 			path = SwifterTestsCommon;
 			sourceTree = "<group>";
@@ -628,6 +632,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */,
+				7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -637,6 +642,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */,
+				7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87851C660ED60068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 13 - 0
SwifterSampleOSX/main.swift

@@ -7,9 +7,22 @@
 import Foundation
 import Swifter
 
+print(NSBundle.mainBundle().resourcePath!)
+
+enum Route {
+    
+    case Vegetables(data: Int) = {
+    
+        return 1
+    }
+}
+
+
+
 let server = demoServer(NSBundle.mainBundle().resourcePath!)
 
 do {
+    server["/sharedir/:path"] = HttpHandlers.shareFilesFromDirectory("/Users/damiankolakowski/Desktop")
     server["/SwiftyJSON"] = { request in
         let js: JSON = ["return": "OK", "isItAJSON": true, "code" : 200]
         return .OK(.Custom(js, { object in

+ 28 - 2
SwifterSampleiOS/ViewController.swift

@@ -13,9 +13,35 @@ class ViewController: UIViewController {
     
     override func viewDidLoad() {
         super.viewDidLoad()
-        let server = demoServer(NSBundle.mainBundle().resourcePath)
+        
+        let server = HttpServer();
+        
+        server.get["/upload"] = { r in
+            return .OK(.Html("<form method=\"POST\" action=\"/upload\" enctype=\"multipart/form-data\">" +
+                "<input name=\"my_file\" type=\"file\"/>" +
+                "<button type=\"submit\">Send File</button>" +
+            "</form>"))
+        }
+        server.post["/upload"] = { r in
+            if let myFileMultipart = r.parseMultiPartFormData().filter({ $0.name == "my_file" }).first {
+                guard let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first else {
+                    return .InternalServerError
+                }
+                let data: NSData = myFileMultipart.body.withUnsafeBufferPointer { pointer in
+                    return NSData(bytes: pointer.baseAddress, length: myFileMultipart.body.count)
+                }
+                guard let fileSaveUrl = NSURL(string: "name_for_file.txt", relativeToURL: documentsUrl) else {
+                    return .InternalServerError
+                }
+                print(fileSaveUrl)
+                data.writeToURL(fileSaveUrl, atomically: true)
+                return .OK(.Html("Your file has been uploaded !"))
+            }
+            return .InternalServerError
+        }
+        
         do {
-            try server.start()
+            try server.start(9099)
         } catch {
             print("Server start error: \(error)")
         }

+ 17 - 18
SwifterTestsCommon/SwifterTestsHttpParser.swift

@@ -6,7 +6,6 @@
 //
 
 import XCTest
-
 import Swifter
 
 class SwifterTestsHttpParser: XCTestCase {
@@ -35,72 +34,72 @@ class SwifterTestsHttpParser: XCTestCase {
         
         do {
             try parser.readHttpRequest(TestSocket(""))
-            XCTAssertTrue(false, "Parser should throw an error if socket is empty.")
+            XCTAssert(false, "Parser should throw an error if socket is empty.")
         } catch { }
 
         do {
             try parser.readHttpRequest(TestSocket("12345678"))
-            XCTAssertTrue(false, "Parser should throw an error if status line has single token.")
+            XCTAssert(false, "Parser should throw an error if status line has single token.")
         } catch { }
 
         do {
             try parser.readHttpRequest(TestSocket("GET HTTP/1.0"))
-            XCTAssertTrue(false, "Parser should throw an error if status line has not enough tokens.")
+            XCTAssert(false, "Parser should throw an error if status line has not enough tokens.")
         } catch { }
 
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
-            XCTAssertTrue(false, "Parser should throw an error if there is no next line symbol.")
+            XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
         } catch { }
             
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
-            XCTAssertTrue(false, "Parser should throw an error if there is no next line symbol.")
+            XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
         } catch { }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r"))
-            XCTAssertTrue(false, "Parser should throw an error if there is no next line symbol.")
+            XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
         } catch { }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\n"))
-            XCTAssertTrue(false, "Parser should throw an error if there is no 'Content-Length' header.")
+            XCTAssert(false, "Parser should throw an error if there is no 'Content-Length' header.")
         } catch { }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"))
         } catch {
-            XCTAssertTrue(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
+            XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
         }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 0\r\n\n"))
         } catch {
-            XCTAssertTrue(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
+            XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
         }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 5\n\n12345"))
         } catch {
-            XCTAssertTrue(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
+            XCTAssert(false, "Parser should not throw any errors if there is a valid 'Content-Length' header.")
         }
         
         do {
             try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 10\r\n\n"))
-            XCTAssertTrue(false, "Parser should throw an error if request' body is too short.")
+            XCTAssert(false, "Parser should throw an error if request' body is too short.")
         } catch { }
         
         var r = try? parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 10\n\n1234567890"))
-        XCTAssertTrue(r?.method == "GET", "Parser should extract HTTP method name from the status line.")
-        XCTAssertTrue(r?.path == "/", "Parser should extract HTTP path value from the status line.")
-        XCTAssertTrue(r?.headers["content-length"] == "10", "Parser should extract Content-Length header value.")
+        XCTAssert(r?.method == "GET", "Parser should extract HTTP method name from the status line.")
+        XCTAssert(r?.path == "/", "Parser should extract HTTP path value from the status line.")
+        XCTAssert(r?.headers["content-length"] == "10", "Parser should extract Content-Length header value.")
         
         r = try? parser.readHttpRequest(TestSocket("POST / HTTP/1.0\nContent-Length: 10\n\n1234567890"))
-        XCTAssertTrue(r?.method == "POST", "Parser should extract HTTP method name from the status line.")
+        XCTAssert(r?.method == "POST", "Parser should extract HTTP method name from the status line.")
         
         r = try? parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nHeader1: 1\nHeader2: 2\nContent-Length: 0\n\n"))
-        XCTAssertTrue(r?.headers["header1"] == "1", "Parser should extract multiple headers from the request.")
-        XCTAssertTrue(r?.headers["header2"] == "2", "Parser should extract multiple headers from the request.")
+        XCTAssert(r?.headers["header1"] == "1", "Parser should extract multiple headers from the request.")
+        XCTAssert(r?.headers["header2"] == "2", "Parser should extract multiple headers from the request.")
     }
 }

+ 0 - 1
SwifterTestsCommon/SwifterTestsStringExtensions.swift

@@ -6,7 +6,6 @@
 //
 
 import XCTest
-
 import Swifter
 
 class SwifterTestsStringExtensions: XCTestCase {

+ 113 - 0
SwifterTestsCommon/SwifterTestsWebSocketSession.swift

@@ -0,0 +1,113 @@
+//
+//  SwifterTests.swift
+//  SwifterTests
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import XCTest
+import Swifter
+
+class SwifterTestsWebSocketSession: XCTestCase {
+    
+    class TestSocket: Socket {
+        var content = [UInt8]()
+        var offset = 0
+        
+        init(_ content: [UInt8]) {
+            super.init(socketFileDescriptor: -1)
+            self.content.appendContentsOf(content)
+        }
+        
+        override func read() throws -> UInt8 {
+            if offset < content.count {
+                let value = self.content[offset]
+                offset = offset + 1
+                return value
+            }
+            throw SocketError.RecvFailed("")
+        }
+    }
+    
+    func testParser() {
+        let session = HttpHandlers.WebSocketSession(TestSocket([]))
+        
+        do {
+            try session.readFrame(TestSocket([0]))
+            XCTAssert(false, "Parser should throw an error if socket has not enough data for a frame.")
+        } catch {
+            XCTAssert(true, "Parser should throw an error if socket has not enough data for a frame.")
+        }
+        
+        do {
+            try session.readFrame(TestSocket([0b0000_0001, 0b0000_0000, 0, 0, 0, 0]))
+            XCTAssert(false, "Parser should not accept unmasked frames.")
+        } catch HttpHandlers.WebSocketSession.Error.UnMaskedFrame {
+            XCTAssert(true, "Parse should throw UnMaskedFrame error for unmasked message.")
+        } catch {
+            XCTAssert(false, "Parse should throw UnMaskedFrame error for unmasked message.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b1000_0001, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssert(frame.fin, "Parser should detect fin flag set.")
+        } catch {
+            XCTAssert(false, "Parser should not throw an error for a frame with fin flag set (\(error)")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_0000, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Continue, "Parser should accept Continue opcode.")
+        } catch {
+            XCTAssertTrue(true, "Parser should accept Continue opcode without any errors.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_0001, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Text, "Parser should accept Text opcode.")
+        } catch {
+            XCTAssert(false, "Parser should accept Text opcode without any errors.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_0010, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Binary, "Parser should accept Binary opcode.")
+        } catch {
+            XCTAssert(false, "Parser should accept Binary opcode without any errors.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_1000, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Close, "Parser should accept Close opcode.")
+        } catch {
+            XCTAssert(false, "Parser should accept Close opcode without any errors.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_1001, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Ping, "Parser should accept Ping opcode.")
+        } catch {
+            XCTAssert(false, "Parser should accept Ping opcode without any errors.")
+        }
+        
+        do {
+            let frame = try session.readFrame(TestSocket([0b0000_1010, 0b1000_0000, 0, 0, 0, 0]))
+            XCTAssertEqual(frame.opcode, HttpHandlers.WebSocketSession.OpCode.Pong, "Parser should accept Pong opcode.")
+        } catch {
+            XCTAssert(false, "Parser should accept Pong opcode without any errors.")
+        }
+        
+        for var opcode in [3, 4, 5, 6, 7, 11, 12, 13, 14, 15] {
+            do {
+                try session.readFrame(TestSocket([UInt8(opcode), 0b1000_0000, 0, 0, 0, 0]))
+                XCTAssert(false, "Parse should throw an error for unknown opcode: \(opcode)")
+            } catch HttpHandlers.WebSocketSession.Error.UnknownOpCode(_) {
+                XCTAssert(true, "Parse should throw UnknownOpCode error for unknown opcode.")
+            } catch {
+                XCTAssert(false, "Parse should throw UnknownOpCode error for unknown opcode (was \(error)).")
+            }
+        }
+        
+    }
+    
+}