فهرست منبع

Merge branch 'master' of https://github.com/zarghol/swifter into zarghol-master

# Conflicts:
#	Common/DemoServer.swift
#	Common/HttpParser.swift
#	Common/HttpResponse.swift
#	Common/HttpServer.swift
Damian Kołakowski 10 سال پیش
والد
کامیت
c9c314c9cd

+ 2 - 3
Common/DemoServer.swift

@@ -1,7 +1,7 @@
 //
 //  DemoServer.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
@@ -71,8 +71,7 @@ func demoServer(publicDir: String?) -> HttpServer {
     }
     server["/"] = { request in
         var listPage = "Available services:<br><ul>"
-        listPage += server.routes().map({ "<li><a href=\"\($0)\">\($0)</a></li>"}).joinWithSeparator("")
-        listPage += "</ul>"
+        listPage += server.routes.map({ "<li><a href=\"\($0)\">\($0)</a></li>"}).joinWithSeparator("")
         return .OK(.HTML(listPage))
     }
     return server

+ 4 - 3
Common/HttpHandlers.swift

@@ -1,7 +1,7 @@
 //
 //  Handlers.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
@@ -16,6 +16,7 @@ public class HttpHandlers {
                     return HttpResponse.RAW(200, "OK", nil, fileBody)
                 }
             }
+            
             return HttpResponse.NotFound
         }
     }
@@ -26,8 +27,8 @@ public class HttpHandlers {
                 let filePath = dir.stringByExpandingTildeInPath.stringByAppendingPathComponent(pathFromUrl)
                 let fileManager = NSFileManager.defaultManager()
                 var isDir: ObjCBool = false;
-                if ( fileManager.fileExistsAtPath(filePath, isDirectory: &isDir) ) {
-                    if ( isDir ) {
+                if fileManager.fileExistsAtPath(filePath, isDirectory: &isDir) {
+                    if isDir {
                         do {
                             let files = try fileManager.contentsOfDirectoryAtPath(filePath)
                             var response = "<h3>\(filePath)</h3></br><table>"

+ 57 - 57
Common/HttpParser.swift

@@ -1,62 +1,61 @@
 //
 //  HttpParser.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
 
+
+enum HttpParserError : ErrorType {
+    case RecvFailed(String?)
+    case ReadBodyFailed(String?)
+    case InvalidStatusLine(String)
+}
+
 class HttpParser {
     
-    func err(reason: String) -> NSError {
-        return NSError(domain: "HttpParser", code: 0, userInfo: [NSLocalizedDescriptionKey : reason])
-    }
-    
-    func nextHttpRequest(socket: CInt, error:NSErrorPointer = nil) -> HttpRequest? {
-        if let statusLine = nextLine(socket, error: error) {
-            let statusTokens = statusLine.componentsSeparatedByString(" ")
-            print(statusTokens)
-            if ( statusTokens.count < 3 ) {
-                if error != nil { error.memory = err("Invalid status line: \(statusLine)") }
-                return nil
-            }
-            let method = statusTokens[0]
-            let path = statusTokens[1]
-            let urlParams = extractUrlParams(path)
-            if let headers = nextHeaders(socket, error: error) {
-                if let contentLength = headers["content-length"], let contentLengthValue = Int(contentLength) {
-                    let body = nextBody(socket, size: contentLengthValue, error: error)
-                    return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: body, capturedUrlGroups: [], address: nil)
-                }
-                return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: nil, capturedUrlGroups: [], address: nil)
-            }
+    func nextHttpRequest(socket: CInt) throws -> HttpRequest {
+        let statusLine = try nextLine(socket)
+        let statusLineTokens = statusLine.componentsSeparatedByString(" ")
+        print(statusLineTokens)
+        if statusLineTokens.count < 3 {
+            throw HttpParserError.InvalidStatusLine(statusLine)
         }
-        return nil
+        let method = statusLineTokens[0]
+        let path = statusLineTokens[1]
+        let urlParams = extractUrlParams(path)
+        let headers = try nextHeaders(socket)
+        if let contentLength = headers["content-length"], let contentLengthValue = Int(contentLength) {
+            let body = try nextBody(socket, size: contentLengthValue)
+            return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: body, capturedUrlGroups: [], address: nil)
+        }
+        return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: nil, capturedUrlGroups: [], address: nil)
     }
     
     private func extractUrlParams(url: String) -> [(String, String)] {
-        if let query = url.componentsSeparatedByString("?").last {
-            return query.componentsSeparatedByString("&").map { (param:String) -> (String, String) in
-                let tokens = param.componentsSeparatedByString("=")
-                if tokens.count >= 2 {
-                    let key = tokens[0].stringByRemovingPercentEncoding
-                    let value = tokens[1].stringByRemovingPercentEncoding
-                    if let key = key, value = value { return (key, value) }
-                }
-                return ("","")
+        guard let query = url.componentsSeparatedByString("?").last else {
+            return []
+        }
+        return query.componentsSeparatedByString("&").map { (param:String) -> (String, String) in
+            let tokens = param.componentsSeparatedByString("=")
+            guard tokens.count >= 2 else {
+                return ("", "")
+            }
+            guard let k = tokens[0].stringByRemovingPercentEncoding, v = tokens[1].stringByRemovingPercentEncoding else {
+                return ("", "")
             }
+            return (k, v)
         }
-        return []
     }
     
-    private func nextBody(socket: CInt, size: Int , error:NSErrorPointer) -> String? {
+    private func nextBody(socket: CInt, size: Int) throws -> String {
         var body = ""
         var counter = 0;
-        while ( counter < size ) {
+        while counter < size {
             let c = nextInt8(socket)
-            if ( c < 0 ) {
-                if error != nil { error.memory = err("IO error while reading body") }
-                return nil
+            if c < 0 {
+                throw HttpParserError.ReadBodyFailed(String.fromCString(UnsafePointer(strerror(errno))))
             }
             body.append(UnicodeScalar(c))
             counter++;
@@ -64,49 +63,50 @@ class HttpParser {
         return body
     }
     
-    private func nextHeaders(socket: CInt, error:NSErrorPointer) -> Dictionary<String, String>? {
-        var headers = Dictionary<String, String>()
-        while let headerLine = nextLine(socket, error: error) {
-            if ( headerLine.isEmpty ) {
-                return headers
+    private func nextHeaders(socket: CInt) throws -> [String: String] {
+        var requestHeaders = [String: String]()
+        repeat {
+            let headerLine = try nextLine(socket)
+            if headerLine.isEmpty {
+                return requestHeaders
             }
             let headerTokens = headerLine.componentsSeparatedByString(":")
-            if ( headerTokens.count >= 2 ) {
+            if headerTokens.count >= 2 {
                 // RFC 2616 - "Hypertext Transfer Protocol -- HTTP/1.1", paragraph 4.2, "Message Headers":
                 // "Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive."
-                // We can keep lower case version.
+                // We will keep lower case version.
                 let headerName = headerTokens[0].lowercaseString
                 let headerValue = headerTokens[1].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
-                if ( !headerName.isEmpty && !headerValue.isEmpty ) {
-                    headers.updateValue(headerValue, forKey: headerName)
+                if !headerName.isEmpty && !headerValue.isEmpty {
+                    requestHeaders.updateValue(headerValue, forKey: headerName)
                 }
             }
-        }
-        return nil
+        } while true
     }
 
     private func nextInt8(socket: CInt) -> Int {
         var buffer = [UInt8](count: 1, repeatedValue: 0);
         let next = recv(socket as Int32, &buffer, Int(buffer.count), 0)
-        if next <= 0 { return next }
+        if next <= 0 {
+            return next
+        }
         return Int(buffer[0])
     }
     
-    private func nextLine(socket: CInt, error:NSErrorPointer) -> String? {
+    private func nextLine(socket: CInt) throws -> String {
         var characters: String = ""
         var n = 0
         repeat {
             n = nextInt8(socket)
             if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
-        } while ( n > 0 && n != 10 /* NL */)
-        if ( n == -1 && characters.isEmpty ) {
-            if error != nil { error.memory = Socket.lastErr("recv(...) failed.") }
-            return nil
+        } while n > 0 && n != 10 /* NL */
+        if n == -1 {
+            throw HttpParserError.RecvFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
         return characters
     }
     
-    func supportsKeepAlive(headers: Dictionary<String, String>) -> Bool {
+    func supportsKeepAlive(headers: [String: String]) -> Bool {
         if let value = headers["connection"] {
             return "keep-alive" == value.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).lowercaseString
         }

+ 3 - 3
Common/HttpRequest.swift

@@ -1,7 +1,7 @@
 //
 //  HttpRequest.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
@@ -9,7 +9,7 @@ import Foundation
 public struct HttpRequest {
     
     public let url: String
-    public let urlParams: [(String, String)] // http://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys
+    public let urlParams: [(String, String)]
     public let method: String
     public let headers: [String: String]
 	public let body: String?
@@ -30,4 +30,4 @@ public struct HttpRequest {
         }
         return []
     }
-}
+}

+ 8 - 4
Common/HttpResponse.swift

@@ -1,7 +1,7 @@
 //
 //  HttpResponse.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
@@ -16,6 +16,7 @@ public enum HttpResponseBody {
     
     func data() -> String? {
         switch self {
+            
         case .JSON(let object):
             if NSJSONSerialization.isValidJSONObject(object) {
                 do {
@@ -28,8 +29,10 @@ public enum HttpResponseBody {
                 }
             }
             return "Invalid object to serialise."
+            
         case .XML(_):
             return "XML serialization not supported."
+            
         case .PLIST(let object):
             let format = NSPropertyListFormat.XMLFormat_v1_0
             if NSPropertyListSerialization.propertyList(object, isValidForFormat: format) {
@@ -43,8 +46,10 @@ public enum HttpResponseBody {
                 }
             }
             return "Invalid object to serialise."
+            
         case .STRING(let body):
             return body
+            
         case .HTML(let body):
             return "<html><meta charset=\"UTF-8\"><body>\(body)</body></html>"
         }
@@ -103,9 +108,8 @@ public enum HttpResponse {
                 // source XML document -- is readable by casual users, text/xml is preferable to application/xml. 
                 // MIME user agents (and web user agents) that do not have explicit support for text/xml will treat it as text/plain, 
                 // for example, by displaying the XML MIME entity as plain text.
-                // Application/xml is preferable when the XML MIME entity is unreadable by casual users."
                 case .HTML(_)   : headers["Content-Type"] = "text/html"
-                default:[]
+                default:break
             }
         case .MovedPermanently(let location): headers["Location"] = location
         case .RAW(_,_, let rawHeaders,_):
@@ -114,7 +118,7 @@ public enum HttpResponse {
                     headers.updateValue(v, forKey: k)
                 }
             }
-        default:[]
+        default:break
         }
         return headers
     }

+ 67 - 62
Common/HttpServer.swift

@@ -1,7 +1,7 @@
 //
 //  HttpServer.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
@@ -12,10 +12,11 @@ public class HttpServer
     
     public typealias Handler = HttpRequest -> HttpResponse
     
-    var handlers: [(expression: NSRegularExpression, handler: Handler)] = []
-    var clientSockets: Set<CInt> = []
-    let clientSocketsLock = 0
-    var acceptSocket: CInt = -1
+    private(set) var handlers: [(expression: NSRegularExpression, handler: Handler)] = []
+    private(set) var acceptSocket: CInt = -1
+    private(set) var clientSockets: Set<CInt> = []
+    private let clientSocketsLock = 0
+
     
     let matchingOptions = NSMatchingOptions(rawValue: 0)
     let expressionOptions = NSRegularExpressionOptions(rawValue: 0)
@@ -38,65 +39,70 @@ public class HttpServer
         }
     }
     
-    public func routes() -> [String] { return handlers.map { $0.0.pattern } }
+    public var routes:[String] {
+        return self.handlers.map { $0.0.pattern }
+    }
     
-    public func start(listenPort: in_port_t = 8080, error: NSErrorPointer = nil) -> Bool {
-        stop()
-        if let socket = Socket.tcpForListen(listenPort, error: error) {
-            self.acceptSocket = socket
-            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
-                while let socket = Socket.acceptClientSocket(self.acceptSocket) {
-                    HttpServer.lock(self.clientSocketsLock) {
-                        self.clientSockets.insert(socket)
-                    }
-                    if self.acceptSocket == -1 { return }
-                    let socketAddress = Socket.peername(socket)
-                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
-                        let parser = HttpParser()
-                        while let request = parser.nextHttpRequest(socket) {
-                            let keepAlive = parser.supportsKeepAlive(request.headers)
-                            if let (expression, handler) = self.findHandler(request.url) {
-                                let capturedUrlsGroups = self.captureExpressionGroups(expression, value: request.url)
-                                let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, capturedUrlGroups: capturedUrlsGroups, address: socketAddress)
-                                HttpServer.respond(socket, response: handler(updatedRequest), keepAlive: keepAlive)
-                            } else {
-                                HttpServer.respond(socket, response: HttpResponse.NotFound, keepAlive: keepAlive)
-                            }
-                            if !keepAlive { break }
+    public func start(listenPort: in_port_t = 8080) throws {
+        self.stop()
+        self.acceptSocket = try Socket.tcpForListen(listenPort)
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
+            while let socket = try? Socket.acceptClientSocket(self.acceptSocket) {
+                HttpServer.lock(self.clientSocketsLock) {
+                    self.clientSockets.insert(socket)
+                }
+                if self.acceptSocket == -1 { return }
+                let socketAddress = try? Socket.peername(socket)!
+                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
+                    let parser = HttpParser()
+                    while let request = try? parser.nextHttpRequest(socket) {
+                        let keepAlive = parser.supportsKeepAlive(request.headers)
+                        let response: HttpResponse
+                        if let (expression, handler) = self.findHandler(request.url) {
+                            let capturedUrlsGroups = self.captureExpressionGroups(expression, value: request.url)
+                            let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, capturedUrlGroups: capturedUrlsGroups, address: socketAddress)
+                            response = handler(updatedRequest)
+                        } else {
+                            response = HttpResponse.NotFound
                         }
-                        Socket.release(socket)
-                        HttpServer.lock(self.clientSocketsLock) {
-                            self.clientSockets.remove(socket)
+                        do {
+                            try HttpServer.respond(socket, response: response, keepAlive: keepAlive)
+                        } catch {
+                            print("Failed to send response: \(error)")
+                            return
                         }
-                    })
+                        if !keepAlive { break }
+                    }
+                    Socket.release(socket)
+                    HttpServer.lock(self.clientSocketsLock) {
+                        self.clientSockets.remove(socket)
+                    }
                 }
-                self.stop()
-            })
-            return true
+            }
+            self.stop()
         }
-        return false
     }
     
     func findHandler(url:String) -> (NSRegularExpression, Handler)? {
-		let u = NSURL(string: url)!
-		let path = u.path!
-		for handler in self.handlers {
-			let regex = handler.0
-            let matches = regex.numberOfMatchesInString(path, options: self.matchingOptions, range: HttpServer.asciiRange(path)) > 0
-			if matches {
-				return handler;
-			}
+        if let u = NSURL(string: url), path = u.path {
+            for handler in self.handlers {
+                if handler.expression.numberOfMatchesInString(path, options: self.matchingOptions, range: HttpServer.asciiRange(path)) > 0 {
+                    return handler
+                }
+            }
         }
 		return nil
     }
     
     func captureExpressionGroups(expression: NSRegularExpression, value: String) -> [String] {
-		let u = NSURL(string: value)!
-		let path = u.path!
+        guard let u = NSURL(string: value), path = u.path else {
+            return []
+        }
+        
         var capturedGroups = [String]()
         if let result = expression.firstMatchInString(path, options: matchingOptions, range: HttpServer.asciiRange(path)) {
             let nsValue: NSString = path
-            for var i = 1 ; i < result.numberOfRanges ; ++i {
+            for i in 1..<result.numberOfRanges {
                 if let group = nsValue.substringWithRange(result.rangeAtIndex(i)).stringByRemovingPercentEncoding {
                     capturedGroups.append(group)
                 }
@@ -107,7 +113,7 @@ public class HttpServer
     
     public func stop() {
         Socket.release(acceptSocket)
-        acceptSocket = -1
+        self.acceptSocket = -1
         HttpServer.lock(self.clientSocketsLock) {
             for clientSocket in self.clientSockets {
                 Socket.release(clientSocket)
@@ -116,32 +122,31 @@ public class HttpServer
         }
     }
     
-    public class func asciiRange(value: String) -> NSRange {
+    private class func asciiRange(value: String) -> NSRange {
         return NSMakeRange(0, value.lengthOfBytesUsingEncoding(NSASCIIStringEncoding))
     }
     
-    public class func lock(handle: AnyObject, closure: () -> ()) {
+    private class func lock(handle: AnyObject, closure: () -> ()) {
         objc_sync_enter(handle)
         closure()
         objc_sync_exit(handle)
     }
     
-    public class func respond(socket: CInt, response: HttpResponse, keepAlive: Bool) {
-        Socket.writeUTF8(socket, string: "HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
-        if let body = response.body() {
-            Socket.writeASCII(socket, string: "Content-Length: \(body.length)\r\n")
-        } else {
-            Socket.writeASCII(socket, string: "Content-Length: 0\r\n")
-        }
+    private class func respond(socket: CInt, response: HttpResponse, keepAlive: Bool) throws {
+        try Socket.writeUTF8(socket, string: "HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
+        
+        let length = response.body()?.length ?? 0
+        try Socket.writeASCII(socket, string: "Content-Length: \(length)\r\n")
+        
         if keepAlive {
-            Socket.writeASCII(socket, string: "Connection: keep-alive\r\n")
+            try Socket.writeASCII(socket, string: "Connection: keep-alive\r\n")
         }
         for (name, value) in response.headers() {
-            Socket.writeASCII(socket, string: "\(name): \(value)\r\n")
+            try Socket.writeASCII(socket, string: "\(name): \(value)\r\n")
         }
-        Socket.writeASCII(socket, string: "\r\n")
+        try Socket.writeASCII(socket, string: "\r\n")
         if let body = response.body() {
-            Socket.writeData(socket, data: body)
+            try Socket.writeData(socket, data: body)
         }
     }
 }

+ 55 - 49
Common/Socket.swift

@@ -1,91 +1,99 @@
 //
 //  Socket.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
 
 /* Low level routines for POSIX sockets */
 
+enum SocketError: ErrorType {
+    case SocketInitializationFailed(String?)
+    case SocketOptionInitializationFailed(String?)
+    case BindFailed(String?)
+    case ListenFailed(String?)
+    case WriteFailed(String?)
+    case GetPeerNameFailed(String?)
+    case GetNameInfoFailed(String?)
+    case AcceptFailed(String?)
+}
+
+let maxPendingConnection: Int32 = 20
+
 struct Socket {
-        
-    static func lastErr(reason: String) -> NSError {
-        let errorCode = errno
-        if let errorText = String.fromCString(UnsafePointer(strerror(errorCode))) {
-            return NSError(domain: "SOCKET", code: Int(errorCode), userInfo: [NSLocalizedFailureReasonErrorKey : reason, NSLocalizedDescriptionKey : errorText])
-        }
-        return NSError(domain: "SOCKET", code: Int(errorCode), userInfo: nil)
-    }
     
-    static func tcpForListen(port: in_port_t = 8080, error: NSErrorPointer = nil) -> CInt? {
+    static func tcpForListen(port: in_port_t = 8080) throws -> CInt {
         let s = socket(AF_INET, SOCK_STREAM, 0)
-        if ( s == -1 ) {
-            if error != nil { error.memory = lastErr("socket(...) failed.") }
-            return nil
+        if s == -1 {
+            throw SocketError.SocketInitializationFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
-        var value: Int32 = 1;
-        if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 ) {
+        
+        var value: Int32 = 1
+        if setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
             release(s)
-            if error != nil { error.memory = lastErr("setsockopt(...) failed.") }
-            return nil
+            throw SocketError.SocketOptionInitializationFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
+        
         nosigpipe(s)
-        var addr = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET),
-            sin_port: port_htons(port), sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
         
+        var addr = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)),
+            sin_family: sa_family_t(AF_INET),
+            sin_port: port_htons(port),
+            sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")),
+            sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
         var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
         memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
-        if ( bind(s, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 ) {
+        
+        if bind(s, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
             release(s)
-            if error != nil { error.memory = lastErr("bind(...) failed.") }
-            return nil
+            throw SocketError.BindFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
-        if ( listen(s, 20 /* max pending connection */ ) == -1 ) {
+        
+        if listen(s, maxPendingConnection ) == -1 {
             release(s)
-            if error != nil { error.memory = lastErr("listen(...) failed.") }
-            return nil
+            throw SocketError.ListenFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
         return s
     }
     
-    static func writeUTF8(socket: CInt, string: String, error: NSErrorPointer = nil) -> Bool {
+    static func writeUTF8(socket: CInt, string: String) throws {
         if let nsdata = string.dataUsingEncoding(NSUTF8StringEncoding) {
-            return writeData(socket, data: nsdata, error: error)
+            try writeData(socket, data: nsdata)
+        } else {
+            throw SocketError.WriteFailed("dataUsingEncoding(NSUTF8StringEncoding) failed")
         }
-        return true
     }
     
-    static func writeASCII(socket: CInt, string: String, error: NSErrorPointer = nil) -> Bool {
+    static func writeASCII(socket: CInt, string: String) throws {
         if let nsdata = string.dataUsingEncoding(NSASCIIStringEncoding) {
-            return writeData(socket, data: nsdata, error: error)
+            try writeData(socket, data: nsdata)
+        } else {
+            throw SocketError.WriteFailed("dataUsingEncoding(NSASCIIStringEncoding) failed")
         }
-        return true
     }
     
-    static func writeData(socket: CInt, data: NSData, error: NSErrorPointer = nil) -> Bool {
+    static func writeData(socket: CInt, data: NSData) throws {
         var sent = 0
         let unsafePointer = UnsafePointer<UInt8>(data.bytes)
-        while ( sent < data.length ) {
+        while sent < data.length {
             let s = write(socket, unsafePointer + sent, Int(data.length - sent))
-            if ( s <= 0 ) {
-                if error != nil { error.memory = lastErr("write(...) failed.") }
-                return false
+            if s <= 0 {
+                throw SocketError.WriteFailed(String.fromCString(UnsafePointer(strerror(errno))))
             }
             sent += s
         }
-        return true
     }
     
-    static func acceptClientSocket(socket: CInt, error:NSErrorPointer = nil) -> CInt? {
-        var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), len: socklen_t = 0
+    static func acceptClientSocket(socket: CInt) throws -> CInt {
+        var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+        var len: socklen_t = 0
         let clientSocket = accept(socket, &addr, &len)
-        if ( clientSocket != -1 ) {
-            Socket.nosigpipe(clientSocket)
-            return clientSocket
+        if clientSocket == -1 {
+            throw SocketError.AcceptFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
-        if error != nil { error.memory = lastErr("accept(...) failed.") }
-        return nil
+        Socket.nosigpipe(clientSocket)
+        return clientSocket
     }
     
     static func nosigpipe(socket: CInt) {
@@ -104,16 +112,14 @@ struct Socket {
         close(socket)
     }
     
-    static func peername(socket: CInt, error: NSErrorPointer = nil) -> String? {
+    static func peername(socket: CInt) throws -> String? {
         var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
         if getpeername(socket, &addr, &len) != 0 {
-            if error != nil { error.memory = lastErr("getpeername(...) failed.") }
-            return nil
+            throw SocketError.GetPeerNameFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
         var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
         if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
-            if error != nil { error.memory = lastErr("getnameinfo(...) failed.") }
-            return nil
+            throw SocketError.GetNameInfoFailed(String.fromCString(UnsafePointer(strerror(errno))))
         }
         return String.fromCString(hostBuffer)
     }

+ 27 - 0
Common/TestSocket.swift

@@ -0,0 +1,27 @@
+//
+//  TestSocket.swift
+//  Swifter
+//
+//  Created by Clément Nonn on 16/10/2015.
+//  Copyright © 2015 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+//http://socket.io/get-started/chat/
+
+func testSocket(publicDir: String) -> HttpServer {
+    let server = HttpServer()
+
+    server["/resources/(.+)"] = HttpHandlers.directory(publicDir)
+    
+    server["/"] = { request in
+        if let html = NSData(contentsOfFile:"\(publicDir)/chat.html") {
+            return HttpResponse.RAW(200, "OK", nil, html)
+        } else {
+            return .NotFound
+        }
+    }
+    
+    return server
+}

+ 22 - 0
Resources/chat.html

@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+    <head>
+        <title>SwiftyIO chat</title>
+        <style>
+            * { margin: 0; padding: 0; box-sizing: border-box; }
+            body { font: 13px Helvetica, Arial; }
+            form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
+            form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
+            form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
+            #messages { list-style-type: none; margin: 0; padding: 0; }
+            #messages li { padding: 5px 10px; }
+            #messages li:nth-child(odd) { background: #eee; }
+            </style>
+    </head>
+    <body>
+        <ul id="messages"></ul>
+        <form action="">
+            <input id="m" autocomplete="off" /><button>Send</button>
+        </form>
+    </body>
+</html>

+ 119 - 0
Swifter.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		18447C3F1BD14CF50011C707 /* TestSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18447C3E1BD14CF50011C707 /* TestSocket.swift */; settings = {ASSET_TAGS = (); }; };
 		7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7C839B7419422CFF003A6950 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C839B7319422CFF003A6950 /* AppDelegate.swift */; };
@@ -31,6 +32,19 @@
 		7CB102DD1A167FFA00CBA3B4 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB102DC1A167FFA00CBA3B4 /* DemoServer.swift */; };
 		7CB102DE1A1680EA00CBA3B4 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB102DC1A167FFA00CBA3B4 /* DemoServer.swift */; };
 		7CB102E01A17381D00CBA3B4 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
+		7CEA69AA1BD270F10009F783 /* chat.html in Resources */ = {isa = PBXBuildFile; fileRef = 7CEA69A91BD270F10009F783 /* chat.html */; settings = {ASSET_TAGS = (); }; };
+		7CEA69AF1BD271920009F783 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4814B19A2EED00030B30D /* HttpResponse.swift */; };
+		7CEA69B11BD271920009F783 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4814A19A2EED00030B30D /* HttpParser.swift */; };
+		7CEA69B21BD271920009F783 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4814D19A2EED00030B30D /* Socket.swift */; };
+		7CEA69B31BD271920009F783 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4814C19A2EED00030B30D /* HttpServer.swift */; };
+		7CEA69B41BD271920009F783 /* chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CEA69AB1BD271160009F783 /* chat.swift */; settings = {ASSET_TAGS = (); }; };
+		7CEA69B61BD271920009F783 /* TestSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18447C3E1BD14CF50011C707 /* TestSocket.swift */; settings = {ASSET_TAGS = (); }; };
+		7CEA69B71BD271920009F783 /* HttpHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB102D91A1664B200CBA3B4 /* HttpHandlers.swift */; };
+		7CEA69B81BD271920009F783 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4815A19A2F6A60030B30D /* HttpRequest.swift */; };
+		7CEA69BB1BD271920009F783 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
+		7CEA69BC1BD271920009F783 /* login.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
+		7CEA69BD1BD271920009F783 /* test.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CA4815719A2EF2B0030B30D /* test.json */; };
+		7CEA69C21BD271B20009F783 /* chat.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CEA69A91BD270F10009F783 /* chat.html */; };
 		98630C071A1C9A9D00478D08 /* login.html in Resources */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
 /* End PBXBuildFile section */
 
@@ -47,9 +61,23 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		7CEA69BA1BD271920009F783 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 12;
+			dstPath = "";
+			dstSubfolderSpec = 7;
+			files = (
+				7CEA69C21BD271B20009F783 /* chat.html in CopyFiles */,
+				7CEA69BB1BD271920009F783 /* logo.png in CopyFiles */,
+				7CEA69BC1BD271920009F783 /* login.html in CopyFiles */,
+				7CEA69BD1BD271920009F783 /* test.json in CopyFiles */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		18447C3E1BD14CF50011C707 /* TestSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestSocket.swift; sourceTree = "<group>"; };
 		7C839B6E19422CFF003A6950 /* Swifter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Swifter.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		7C839B7219422CFF003A6950 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7C839B7319422CFF003A6950 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -67,6 +95,9 @@
 		7CB102D91A1664B200CBA3B4 /* HttpHandlers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpHandlers.swift; sourceTree = "<group>"; };
 		7CB102DC1A167FFA00CBA3B4 /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = "<group>"; };
 		7CB102DF1A17381D00CBA3B4 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
+		7CEA69A91BD270F10009F783 /* chat.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = chat.html; sourceTree = "<group>"; };
+		7CEA69AB1BD271160009F783 /* chat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = chat.swift; sourceTree = "<group>"; };
+		7CEA69C11BD271920009F783 /* SwifterOSXChat */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwifterOSXChat; sourceTree = BUILT_PRODUCTS_DIR; };
 		98630C061A1C9A9D00478D08 /* login.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = login.html; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -85,6 +116,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		7CEA69B91BD271920009F783 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -104,6 +142,7 @@
 			children = (
 				7C839B6E19422CFF003A6950 /* Swifter.app */,
 				7CA4813B19A2EA8D0030B30D /* SwifterOSX */,
+				7CEA69C11BD271920009F783 /* SwifterOSXChat */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -132,6 +171,7 @@
 			isa = PBXGroup;
 			children = (
 				7CA4813D19A2EA8D0030B30D /* main.swift */,
+				7CEA69AB1BD271160009F783 /* chat.swift */,
 			);
 			path = SwifterOSX;
 			sourceTree = "<group>";
@@ -146,6 +186,7 @@
 				7CA4814C19A2EED00030B30D /* HttpServer.swift */,
 				7CA4814D19A2EED00030B30D /* Socket.swift */,
 				7CB102DC1A167FFA00CBA3B4 /* DemoServer.swift */,
+				18447C3E1BD14CF50011C707 /* TestSocket.swift */,
 			);
 			path = Common;
 			sourceTree = "<group>";
@@ -153,6 +194,7 @@
 		7CA4815619A2EF2B0030B30D /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				7CEA69A91BD270F10009F783 /* chat.html */,
 				98630C061A1C9A9D00478D08 /* login.html */,
 				7CB102DF1A17381D00CBA3B4 /* logo.png */,
 				7CA4815719A2EF2B0030B30D /* test.json */,
@@ -197,6 +239,23 @@
 			productReference = 7CA4813B19A2EA8D0030B30D /* SwifterOSX */;
 			productType = "com.apple.product-type.tool";
 		};
+		7CEA69AD1BD271920009F783 /* SwifterOSXChat */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 7CEA69BE1BD271920009F783 /* Build configuration list for PBXNativeTarget "SwifterOSXChat" */;
+			buildPhases = (
+				7CEA69AE1BD271920009F783 /* Sources */,
+				7CEA69B91BD271920009F783 /* Frameworks */,
+				7CEA69BA1BD271920009F783 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = SwifterOSXChat;
+			productName = SwifterOSX;
+			productReference = 7CEA69C11BD271920009F783 /* SwifterOSXChat */;
+			productType = "com.apple.product-type.tool";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
@@ -230,6 +289,7 @@
 			targets = (
 				7C839B6D19422CFF003A6950 /* Swifter */,
 				7CA4813A19A2EA8D0030B30D /* SwifterOSX */,
+				7CEA69AD1BD271920009F783 /* SwifterOSXChat */,
 			);
 		};
 /* End PBXProject section */
@@ -239,6 +299,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7CEA69AA1BD270F10009F783 /* chat.html in Resources */,
 				7CB102E01A17381D00CBA3B4 /* logo.png in Resources */,
 				7C839B7919422CFF003A6950 /* Main.storyboard in Resources */,
 				98630C071A1C9A9D00478D08 /* login.html in Resources */,
@@ -258,6 +319,7 @@
 				7CB102DA1A1664B200CBA3B4 /* HttpHandlers.swift in Sources */,
 				7CB102DD1A167FFA00CBA3B4 /* DemoServer.swift in Sources */,
 				7C839B7619422CFF003A6950 /* ViewController.swift in Sources */,
+				18447C3F1BD14CF50011C707 /* TestSocket.swift in Sources */,
 				7CA4815419A2EED00030B30D /* Socket.swift in Sources */,
 				7CA4815219A2EED00030B30D /* HttpServer.swift in Sources */,
 				7CA4814E19A2EED00030B30D /* HttpParser.swift in Sources */,
@@ -281,6 +343,21 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		7CEA69AE1BD271920009F783 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				7CEA69AF1BD271920009F783 /* HttpResponse.swift in Sources */,
+				7CEA69B11BD271920009F783 /* HttpParser.swift in Sources */,
+				7CEA69B21BD271920009F783 /* Socket.swift in Sources */,
+				7CEA69B31BD271920009F783 /* HttpServer.swift in Sources */,
+				7CEA69B41BD271920009F783 /* chat.swift in Sources */,
+				7CEA69B61BD271920009F783 /* TestSocket.swift in Sources */,
+				7CEA69B71BD271920009F783 /* HttpHandlers.swift in Sources */,
+				7CEA69B81BD271920009F783 /* HttpRequest.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXVariantGroup section */
@@ -438,6 +515,39 @@
 			};
 			name = Release;
 		};
+		7CEA69BF1BD271920009F783 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ENABLE_MODULES = YES;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+				SWIFT_OBJC_BRIDGING_HEADER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		7CEA69C01BD271920009F783 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ENABLE_MODULES = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+				SWIFT_OBJC_BRIDGING_HEADER = "";
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -468,6 +578,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		7CEA69BE1BD271920009F783 /* Build configuration list for PBXNativeTarget "SwifterOSXChat" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				7CEA69BF1BD271920009F783 /* Debug */,
+				7CEA69C01BD271920009F783 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 7C839B6619422CFF003A6950 /* Project object */;

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


+ 49 - 193
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -202,61 +202,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
+            timestampString = "466774667.297767"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
             startingLineNumber = "45"
             endingLineNumber = "45"
-            landmarkName = "start(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "52"
-            endingLineNumber = "52"
-            landmarkName = "start(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "54"
-            endingLineNumber = "54"
-            landmarkName = "start(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "56"
-            endingLineNumber = "56"
-            landmarkName = "start(_:error:)"
-            landmarkType = "5">
+            landmarkName = "HttpServer"
+            landmarkType = "3">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -266,12 +218,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
+            timestampString = "466774667.297767"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "60"
-            endingLineNumber = "60"
-            landmarkName = "start(_:error:)"
+            startingLineNumber = "47"
+            endingLineNumber = "47"
+            landmarkName = "start(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -282,12 +234,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
+            timestampString = "466774667.297767"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "61"
-            endingLineNumber = "61"
-            landmarkName = "start(_:error:)"
+            startingLineNumber = "48"
+            endingLineNumber = "48"
+            landmarkName = "start(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -307,22 +259,6 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "84"
-            endingLineNumber = "84"
-            landmarkName = "findHandler(_:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
@@ -339,22 +275,6 @@
             landmarkType = "7">
          </BreakpointContent>
       </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "57"
-            endingLineNumber = "57"
-            landmarkName = "start(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
@@ -394,28 +314,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpParser.swift"
-            timestampString = "438130358.291299"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "17"
-            endingLineNumber = "17"
-            landmarkName = "nextHttpRequest(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpParser.swift"
-            timestampString = "466766836.996785"
+            timestampString = "466776187.886072"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "33"
-            endingLineNumber = "33"
-            landmarkName = "nextHttpRequest(_:error:)"
+            startingLineNumber = "19"
+            endingLineNumber = "19"
+            landmarkName = "nextHttpRequest(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -426,12 +330,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpParser.swift"
-            timestampString = "466766836.996785"
+            timestampString = "466776187.886072"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "32"
-            endingLineNumber = "32"
-            landmarkName = "nextHttpRequest(_:error:)"
+            startingLineNumber = "18"
+            endingLineNumber = "18"
+            landmarkName = "nextHttpRequest(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -458,12 +362,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
+            timestampString = "466775670.320344"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "117"
-            endingLineNumber = "117"
-            landmarkName = "stop()"
+            startingLineNumber = "105"
+            endingLineNumber = "105"
+            landmarkName = "captureExpressionGroups(_:value:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -474,13 +378,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465835193.153472"
+            timestampString = "466775670.320344"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "118"
-            endingLineNumber = "118"
-            landmarkName = "HttpServer"
-            landmarkType = "3">
+            startingLineNumber = "106"
+            endingLineNumber = "106"
+            landmarkName = "captureExpressionGroups(_:value:)"
+            landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -506,12 +410,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/Socket.swift"
-            timestampString = "438247668.960213"
+            timestampString = "466776889.884253"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "55"
-            endingLineNumber = "55"
-            landmarkName = "writeUTF8(_:string:error:)"
+            startingLineNumber = "28"
+            endingLineNumber = "28"
+            landmarkName = "tcpForListen(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -766,38 +670,6 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpParser.swift"
-            timestampString = "448798148.791495"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "16"
-            endingLineNumber = "16"
-            landmarkName = "nextHttpRequest(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpParser.swift"
-            timestampString = "466766836.996785"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "26"
-            endingLineNumber = "26"
-            landmarkName = "nextHttpRequest(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
@@ -821,12 +693,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/Socket.swift"
-            timestampString = "456081776.855702"
+            timestampString = "466776889.884253"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "108"
-            endingLineNumber = "108"
-            landmarkName = "peername(_:error:)"
+            startingLineNumber = "80"
+            endingLineNumber = "80"
+            landmarkName = "writeData(_:data:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -837,13 +709,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/Socket.swift"
-            timestampString = "456083512.241377"
+            timestampString = "466776889.884253"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "115"
-            endingLineNumber = "115"
-            landmarkName = "peername(_:error:)"
-            landmarkType = "5">
+            startingLineNumber = "87"
+            endingLineNumber = "87"
+            landmarkName = "Socket"
+            landmarkType = "3">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -853,28 +725,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/Socket.swift"
-            timestampString = "456083787.052298"
+            timestampString = "466776889.884253"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "117"
-            endingLineNumber = "117"
-            landmarkName = "peername(_:error:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Common/HttpResponse.swift"
-            timestampString = "466766836.996785"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "112"
-            endingLineNumber = "112"
-            landmarkName = "headers()"
+            startingLineNumber = "89"
+            endingLineNumber = "89"
+            landmarkName = "acceptClientSocket(_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
@@ -885,12 +741,12 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Common/HttpServer.swift"
-            timestampString = "465836185.461661"
+            timestampString = "466775670.320344"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "130"
-            endingLineNumber = "130"
-            landmarkName = "respond(_:response:keepAlive:)"
+            startingLineNumber = "117"
+            endingLineNumber = "117"
+            landmarkName = "stop()"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>

+ 10 - 0
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -14,6 +14,11 @@
 			<key>orderHint</key>
 			<integer>0</integer>
 		</dict>
+		<key>SwifterOSX copy.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>3</integer>
+		</dict>
 		<key>SwifterOSX.xcscheme</key>
 		<dict>
 			<key>orderHint</key>
@@ -42,6 +47,11 @@
 			<key>primary</key>
 			<true/>
 		</dict>
+		<key>7CEA69AD1BD271920009F783</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
 	</dict>
 </dict>
 </plist>

+ 1 - 1
Swifter/AppDelegate.swift

@@ -1,7 +1,7 @@
 //
 //  AppDelegate.swift
 //  TestSwift
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation

+ 5 - 5
Swifter/ViewController.swift

@@ -1,7 +1,7 @@
 //
 //  ViewController.swift
 //  Swifter
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
 import UIKit
@@ -12,10 +12,10 @@ class ViewController: UIViewController {
     
     override func viewDidLoad() {
         super.viewDidLoad()
-        let server = demoServer(NSBundle.mainBundle().resourcePath)
-        self.server = server
-        var error: NSError?
-        if !server.start(error: &error) {
+        self.server = demoServer(NSBundle.mainBundle().resourcePath)
+        do {
+            try self.server.start()
+        } catch {
             print("Server start error: \(error)")
         }
     }

+ 19 - 0
SwifterOSX/chat.swift

@@ -0,0 +1,19 @@
+//
+//  chat.swift
+//  Swifter
+//
+//  Created by Damian Kolakowski on 17/10/15.
+//  Copyright © 2015 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+let server = testSocket(NSBundle.mainBundle().resourcePath!)
+
+do {
+    try server.start(9080)
+    print("Server has started ( port = 9080 ). Try to connect now...")
+    NSRunLoop.mainRunLoop().run()
+} catch {
+    print("Server start error: \(error)")
+}

+ 7 - 8
SwifterOSX/main.swift

@@ -1,20 +1,19 @@
 //
 //  main.swift
 //  SwifterOSX
-//  Copyright (c) 2014 Damian Kołakowski. All rights reserved.
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
 //
 
-
 import Foundation
 
-var error: NSError?
-let server = demoServer(NSBundle.mainBundle().resourcePath)
+let server = demoServer(NSBundle.mainBundle().resourcePath!)
 
-if !server.start(9080, error: &error) {
-    print("Server start error: \(error)")
-} else {
-    print("Server started. Try a connection now...")
+do {
+    try server.start(9080)
+    print("Server has started ( port = 9080 ). Try to connect now...")
     NSRunLoop.mainRunLoop().run()
+} catch {
+    print("Server start error: \(error)")
 }