Răsfoiți Sursa

Merge remote-tracking branch 'upstream/stable' into stable

Daniel Bauke 9 ani în urmă
părinte
comite
b250a0d27f

+ 1 - 0
Sources/HttpResponse.swift

@@ -20,6 +20,7 @@ public protocol HttpResponseBodyWriter {
     func write(file: File) throws
     func write(data: [UInt8]) throws
     func write(data: ArraySlice<UInt8>) throws
+    func write(data: NSData) throws
 }
 
 public enum HttpResponseBody {

+ 4 - 4
Sources/HttpRouter.swift

@@ -100,11 +100,11 @@ public class HttpRouter {
             params[variableNode.0] = pathToken
             return findHandler(&node.nodes[variableNode.0]!, params: &params, generator: &generator)
         }
-        if let _ = node.nodes[pathToken] {
-            return findHandler(&node.nodes[pathToken]!, params: &params, generator: &generator)
+        if var node = node.nodes[pathToken] {
+            return findHandler(&node, params: &params, generator: &generator)
         }
-        if let _ = node.nodes["*"] {
-            return findHandler(&node.nodes["*"]!, params: &params, generator: &generator)
+        if var node = node.nodes["*"] {
+            return findHandler(&node, params: &params, generator: &generator)
         }
         return nil
     }

+ 31 - 12
Sources/HttpServerIO.swift

@@ -15,8 +15,17 @@ public class HttpServerIO {
     
     private var socket = Socket(socketFileDescriptor: -1)
     private var sockets = Set<Socket>()
-    public private(set) var state = HttpServerIOState.Stopped
+    private var stateValue: Int32 = HttpServerIOState.Stopped.rawValue
+    public private(set) var state: HttpServerIOState {
+        get {
+            return HttpServerIOState(rawValue: stateValue)!
+        }
+        set(state) {
+            OSAtomicCompareAndSwapInt(self.state.rawValue, state.rawValue, &stateValue)
+        }
+    }
     public var operating: Bool { get { return self.state == .Running } }
+    private let queue = dispatch_queue_create("swifter.httpserverio.clientsockets", DISPATCH_QUEUE_SERIAL)
     
     public func port() throws -> Int {
         return Int(try socket.port())
@@ -36,18 +45,22 @@ public class HttpServerIO {
         self.state = .Starting
         self.socket = try Socket.tcpSocketForListen(port, forceIPv4: forceIPv4)
         dispatch_async(dispatch_get_global_queue(priority, 0)) { [weak self] in
-            guard let sself = self else { return }
-            guard sself.operating else { return }
-            while let socket = try? sself.socket.acceptClientSocket() {
+            guard let `self` = self else { return }
+            guard self.operating else { return }
+            while let socket = try? self.socket.acceptClientSocket() {
                 dispatch_async(dispatch_get_global_queue(priority, 0), { [weak self] in
-                    guard let sself = self else { return }
-                    guard sself.operating else { return }
-                    sself.sockets.insert(socket)
-                    sself.handleConnection(socket)
-                    sself.sockets.remove(socket)
+                    guard let `self` = self else { return }
+                    guard self.operating else { return }
+                    dispatch_async(self.queue) {
+                        self.sockets.insert(socket)
+                    }
+                    self.handleConnection(socket)
+                    dispatch_async(self.queue) {
+                        self.sockets.remove(socket)
+                    }
                 })
             }
-            sself.stop()
+            self.stop()
         }
         self.state = .Running
     }
@@ -59,7 +72,9 @@ public class HttpServerIO {
         for socket in self.sockets {
             socket.shutdwn()
         }
-        self.sockets.removeAll(keepCapacity: true)
+        dispatch_sync(queue) {
+            self.sockets.removeAll(keepCapacity: true)
+        }
         socket.release()
         self.state = .Stopped
     }
@@ -109,6 +124,10 @@ public class HttpServerIO {
         func write(data: ArraySlice<UInt8>) throws {
             try socket.writeUInt8(data)
         }
+
+        func write(data: NSData) throws {
+            try socket.writeData(data)
+        }
     }
     
     private func respond(socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
@@ -141,7 +160,7 @@ public class HttpServerIO {
     }
 }
 
-public enum HttpServerIOState: Int{
+public enum HttpServerIOState: Int32 {
     case Starting
     case Running
     case Stopping

+ 29 - 11
Sources/Socket.swift

@@ -30,6 +30,8 @@ public enum SocketError: ErrorType {
 public class Socket: Hashable, Equatable {
         
     let socketFileDescriptor: Int32
+    private var shutdown = false
+
     
     public init(socketFileDescriptor: Int32) {
         self.socketFileDescriptor = socketFileDescriptor
@@ -42,10 +44,18 @@ public class Socket: Hashable, Equatable {
     public var hashValue: Int { return Int(self.socketFileDescriptor) }
     
     public func release() {
+        if shutdown {
+            return
+        }
+        shutdown = true
         Socket.release(self.socketFileDescriptor)
     }
     
     public func shutdwn() {
+        if shutdown {
+            return
+        }
+        shutdown = true
         Socket.shutdwn(self.socketFileDescriptor)
     }
     
@@ -96,18 +106,26 @@ public class Socket: Hashable, Equatable {
     
     public func writeUInt8(data: ArraySlice<UInt8>) throws {
         try data.withUnsafeBufferPointer {
-            var sent = 0
-            while sent < data.count {
-                #if os(Linux)
-                    let s = send(self.socketFileDescriptor, $0.baseAddress + sent, Int(data.count - sent), Int32(MSG_NOSIGNAL))
-                #else
-                    let s = write(self.socketFileDescriptor, $0.baseAddress + sent, Int(data.count - sent))
-                #endif
-                if s <= 0 {
-                    throw SocketError.WriteFailed(Errno.description())
-                }
-                sent += s
+            try writeBuffer($0.baseAddress, length: data.count)
+        }
+    }
+
+    public func writeData(data: NSData) throws {
+        try writeBuffer(data.bytes, length: data.length)
+    }
+
+    private func writeBuffer(pointer: UnsafePointer<Void>, 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
         }
     }
     

+ 14 - 0
XCode/Swifter.xcodeproj/project.pbxproj

@@ -7,6 +7,11 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		0858E7F41D68BB2600491CD1 /* IOSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F31D68BB2600491CD1 /* IOSafetyTests.swift */; };
+		0858E7F51D68BB2600491CD1 /* IOSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F31D68BB2600491CD1 /* IOSafetyTests.swift */; };
+		0858E7F71D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
+		0858E7F81D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
+		0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.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 */; };
@@ -130,6 +135,8 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		0858E7F31D68BB2600491CD1 /* IOSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOSafetyTests.swift; sourceTree = "<group>"; };
+		0858E7F61D68BC2600491CD1 /* PingServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingServer.swift; sourceTree = "<group>"; };
 		269B47A11D3AAAE20042D137 /* Swifter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		269B47A41D3AAC4F0042D137 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		269B47A51D3AAC4F0042D137 /* SwiftertvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftertvOS.h; sourceTree = "<group>"; };
@@ -345,8 +352,10 @@
 			isa = PBXGroup;
 			children = (
 				7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */,
+				0858E7F61D68BC2600491CD1 /* PingServer.swift */,
 				7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */,
 				7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */,
+				0858E7F31D68BB2600491CD1 /* IOSafetyTests.swift */,
 			);
 			path = SwifterTestsCommon;
 			sourceTree = "<group>";
@@ -717,6 +726,7 @@
 			files = (
 				7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */,
 				7CDAB8161BE2A1D400C8A977 /* ViewController.swift in Sources */,
+				0858E7F71D68BC2600491CD1 /* PingServer.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -733,6 +743,8 @@
 			buildActionMask = 2147483647;
 			files = (
 				7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */,
+				0858E7F81D68BC2600491CD1 /* PingServer.swift in Sources */,
+				0858E7F41D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
 				7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);
@@ -743,6 +755,8 @@
 			buildActionMask = 2147483647;
 			files = (
 				7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */,
+				0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */,
+				0858E7F51D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
 				7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87851C660ED60068099B /* SwifterTestsStringExtensions.swift in Sources */,
 			);

+ 40 - 0
XCode/SwifterTestsCommon/IOSafetyTests.swift

@@ -0,0 +1,40 @@
+//
+//  IOSafetyTests.swift
+//  Swifter
+//
+//  Created by Brian Gerstle on 8/20/16.
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import XCTest
+import Swifter
+
+class IOSafetyTests: XCTestCase {
+    var server: HttpServer!
+
+    override func setUp() {
+        super.setUp()
+        server = HttpServer.pingServer()
+    }
+    
+    override func tearDown() {
+        if server.operating {
+            server.stop()
+        }
+        super.tearDown()
+    }
+
+    func testStopWithActiveConnections() {
+        (0...100).forEach { _ in
+            server = HttpServer.pingServer()
+            try! server.start()
+            XCTAssertFalse(NSURLSession.sharedSession().retryPing())
+            (0...100).forEach { _ in
+                dispatch_async(dispatch_get_global_queue(0, 0)) {
+                    NSURLSession.sharedSession().pingTask { _, _, _ in }.resume()
+                }
+            }
+            server.stop()
+        }
+    }
+}

+ 64 - 0
XCode/SwifterTestsCommon/PingServer.swift

@@ -0,0 +1,64 @@
+//
+//  PingServer.swift
+//  Swifter
+//
+//  Created by Brian Gerstle on 8/20/16.
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+import Swifter
+
+// Server
+extension HttpServer {
+    class func pingServer() -> HttpServer {
+        let server = HttpServer()
+        server.GET["/ping"] = { request in
+            return HttpResponse.OK(.Text("pong!"))
+        }
+        return server
+    }
+}
+
+let defaultLocalhost = NSURL(string:"http://localhost:8080")!
+
+// Client
+extension NSURLSession {
+    func pingTask(
+        hostURL: NSURL = defaultLocalhost,
+        completionHandler handler: (NSData?, NSURLResponse?, NSError?) -> Void
+    ) -> NSURLSessionDataTask {
+        return self.dataTaskWithURL(hostURL.URLByAppendingPathComponent("/ping"), completionHandler: handler)
+    }
+    
+    func retryPing(
+        hostURL: NSURL = defaultLocalhost,
+        timeout: Double = 2.0
+    ) -> Bool {
+        let semaphore = dispatch_semaphore_create(0)
+        self.signalIfPongReceived(semaphore, hostURL: hostURL)
+        let timeoutDate = NSDate().dateByAddingTimeInterval(timeout)
+        var timedOut = false
+        while dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) != 0 {
+            if NSDate().laterDate(timeoutDate) != timeoutDate {
+                timedOut = true
+                break
+            }
+            NSRunLoop.currentRunLoop().runMode(
+                NSRunLoopCommonModes,
+                beforeDate: NSDate.distantFuture()
+            )
+        }
+        return timedOut
+    }
+    
+    func signalIfPongReceived(semaphore: dispatch_semaphore_t, hostURL: NSURL) {
+        pingTask(hostURL) { data, response, error in
+            if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200 {
+                dispatch_semaphore_signal(semaphore)
+            } else {
+                self.signalIfPongReceived(semaphore, hostURL: hostURL)
+            }
+        }.resume()
+    }
+}