瀏覽代碼

Merge pull request #1 from httpswift/stable

Update from parent repo
Marcin Maciukiewicz 9 年之前
父節點
當前提交
110ba1f95e
共有 62 個文件被更改,包括 3134 次插入2926 次删除
  1. 1 0
      .swift-version
  2. 55 15
      README.md
  3. 0 11
      Resources/file.html
  4. 0 35
      Resources/login.html
  5. 0 3
      Resources/test.json
  6. 119 58
      Sources/DemoServer.swift
  7. 15 0
      Sources/Errno.swift
  8. 0 125
      Sources/File.swift
  9. 88 0
      Sources/Files.swift
  10. 0 152
      Sources/HttpHandlers+Files.swift
  11. 0 171
      Sources/HttpHandlers+WebSockets.swift
  12. 0 12
      Sources/HttpHandlers.swift
  13. 52 29
      Sources/HttpParser.swift
  14. 44 37
      Sources/HttpRequest.swift
  15. 68 70
      Sources/HttpResponse.swift
  16. 50 27
      Sources/HttpRouter.swift
  17. 6 10
      Sources/HttpServer.swift
  18. 136 106
      Sources/HttpServerIO.swift
  19. 40 0
      Sources/Process.swift
  20. 870 0
      Sources/Scopes.swift
  21. 49 0
      Sources/Socket+File.swift
  22. 115 0
      Sources/Socket+Server.swift
  23. 89 157
      Sources/Socket.swift
  24. 6 10
      Sources/String+BASE64.swift
  25. 144 0
      Sources/String+File.swift
  26. 2 76
      Sources/String+Misc.swift
  27. 44 46
      Sources/String+SHA1.swift
  28. 275 0
      Sources/WebSockets.swift
  29. 2 2
      Swifter.podspec
  30. 0 41
      Swifter.xcodeproj/project.xcworkspace/xcshareddata/Swifter.xccheckout
  31. 0 10
      Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/WorkspaceSettings.xcsettings
  32. 0 92
      Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcschemes/xcschememanagement.plist
  33. 0 1352
      SwifterSampleOSX/SwiftyJSON.swift
  34. 0 30
      SwifterSampleOSX/main.swift
  35. 0 0
      XCode/Resources/logo.png
  36. 362 148
      XCode/Swifter.xcodeproj/project.pbxproj
  37. 0 0
      XCode/Swifter.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  38. 1 1
      XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme
  39. 1 1
      XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwifteriOS.xcscheme
  40. 80 0
      XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwiftertvOS.xcscheme
  41. 1 1
      XCode/SwifterMac/Info.plist
  42. 0 0
      XCode/SwifterMac/SwifterMac.h
  43. 0 0
      XCode/SwifterOSXTests/Info.plist
  44. 28 0
      XCode/SwifterSampleOSX/main.swift
  45. 2 3
      XCode/SwifterSampleiOS/AppDelegate.swift
  46. 0 0
      XCode/SwifterSampleiOS/Base.lproj/Main.storyboard
  47. 0 0
      XCode/SwifterSampleiOS/Images.xcassets/AppIcon.appiconset/Contents.json
  48. 0 0
      XCode/SwifterSampleiOS/Images.xcassets/LaunchImage.launchimage/Contents.json
  49. 1 1
      XCode/SwifterSampleiOS/Info.plist
  50. 0 0
      XCode/SwifterSampleiOS/Launch Screen.storyboard
  51. 1 1
      XCode/SwifterSampleiOS/ViewController.swift
  52. 43 0
      XCode/SwifterTestsCommon/IOSafetyTests.swift
  53. 63 0
      XCode/SwifterTestsCommon/PingServer.swift
  54. 28 27
      XCode/SwifterTestsCommon/SwifterTestsHttpParser.swift
  55. 108 0
      XCode/SwifterTestsCommon/SwifterTestsHttpRouter.swift
  56. 34 38
      XCode/SwifterTestsCommon/SwifterTestsStringExtensions.swift
  57. 36 28
      XCode/SwifterTestsCommon/SwifterTestsWebSocketSession.swift
  58. 28 0
      XCode/SwifteriOS/Info.plist
  59. 0 0
      XCode/SwifteriOS/SwifteriOS.h
  60. 0 0
      XCode/SwifteriOSTests/Info.plist
  61. 28 0
      XCode/SwiftertvOS/Info.plist
  62. 19 0
      XCode/SwiftertvOS/SwiftertvOS.h

+ 1 - 0
.swift-version

@@ -0,0 +1 @@
+3.0

+ 55 - 15
README.md

@@ -1,45 +1,85 @@
-### What is Swift?
-
->Swift is an innovative new programming language for Cocoa and Cocoa Touch. Writing code is interactive and fun, the syntax is concise yet expressive, and apps run lightning-fast. Swift is ready for your next iOS and OS X project — or for addition into your current app — because Swift code works side-by-side with Objective-C.
+![Platform](https://img.shields.io/badge/Platform-Linux%20&%20OSX%20&%20tvOS-4BC51D.svg?style=flat)
+![Swift](https://img.shields.io/badge/Swift-3.x-4BC51D.svg?style=flat)
+![Protocols](https://img.shields.io/badge/Protocols-HTTP%201.1%20&%20WebSockets-4BC51D.svg?style=flat)
+[![CocoaPods](https://img.shields.io/cocoapods/v/Swifter.svg?style=flat)]()
+[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
 
 ### What is Swifter?
 
 Tiny http server engine written in Swift ( https://developer.apple.com/swift/ ) programming language.
 
-![Platform](https://img.shields.io/badge/Platform-Linux%20&%20OSX-4BC51D.svg?style=flat)
-![Swift](https://img.shields.io/badge/Swift-2.2/3.0--dev-4BC51D.svg?style=flat)
-![Protocols](https://img.shields.io/badge/Protocols-HTTP%201.1%20&%20WebSockets-4BC51D.svg?style=flat)
-[![CocoaPods](https://img.shields.io/cocoapods/v/Swifter.svg?style=flat)]()
-[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
+### Branches
+`* stable` - lands on CocoaPods and others. Supports the latest non-beta XCode and SPM. Stable.
+
+`* master` - stable branch plus experimental web-framework layer.
+
+`* 2.0   ` - next version of Swifter (async IO). Experimental.
+
 
 ### How to start?
 ```swift
 let server = HttpServer()
-server["/hello"] = { .OK(.Html("You asked for " + $0.url)) }
+server["/hello"] = { .ok(.html("You asked for \($0)"))  }
 server.start()
 ```
 ### How to share files?
 ```swift
 let server = HttpServer()
-server["/desktop/:path"] = HttpHandlers.shareFilesFromDirectory("/Users/me/Desktop")
+server["/desktop/:path"] = shareFilesFromDirectory("/Users/me/Desktop")
 server.start()
 ```
 ### How to redirect?
 ```swift
 let server = HttpServer()
 server["/redirect"] = { request in
-  return .MovedPermanently("http://www.google.com")
+  return .movedPermanently("http://www.google.com")
 }
 server.start()
 ```
-### CocoaPods? Yes.
+### How to HTML ?
+```swift
+let server = HttpServer()
+server["/my_html"] = scopes { 
+  html {
+    body {
+      h1 { inner = "hello" }
+    }
+  }
+}
+server.start()
 ```
+### How to WebSockets ?
+```swift
+let server = HttpServer()
+server["/websocket-echo"] = websocket({ (session, text) in
+  session.writeText(text)
+}, { (session, binary) in
+  session.writeBinary(binary)
+})
+server.start()
+```
+### CocoaPods? Yes.
+```ruby
+# Use version >= 1.1.0.rc.2 (sudo gem install cocoapods --pre)
 use_frameworks!
-pod 'Swifter', '~> 1.2.0'
+pod 'Swifter', '~> 1.3.3'
 ```
 
 ### Carthage? Also yes.
-
 ```
-github "glock45/swifter" == 1.2.0
+# Use version >= 0.18 (https://github.com/Carthage/Carthage/releases/tag/0.18)
+github "glock45/swifter" == 1.3.3
+```
+
+### Swift Package Manager.
+```swift
+import PackageDescription
+
+let package = Package(
+    name: "MyServer",
+    dependencies: [
+        .Package(url: "https://github.com/httpswift/swifter.git", majorVersion: 1)
+    ]
+)
 ```
+

+ 0 - 11
Resources/file.html

@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-	<body>
-        <form method="POST" action="/upload" enctype="multipart/form-data">
-            <input name="my_file1" type="file"/>
-            <input name="my_file2" type="file"/>
-            <input name="my_file3" type="file"/>
-            <button type="submit">Upload</button>
-        </form>
-	</body>
-</html>

+ 0 - 35
Resources/login.html

@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-  	<head>
-		<link rel="shortcut icon" href="/static/img/favicon.png" />
-    	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<link href="http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" />
-		<script src="http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
-    	<title>Login</title>
-  	</head>
-	<body>
-        <div class="col-md-12">
-            <div class="modal-dialog" style="margin-bottom:0">
-                <div class="modal-content">
-                    <div class="panel-heading">
-                        <h3 class="panel-title">Sign In</h3>
-                    </div>
-                    <div class="panel-body">
-                        <form role="form" method="POST" action="/login">
-                            <fieldset>
-                                <div class="form-group">
-                                    <input class="form-control" placeholder="E-mail" name="email" type="email" autofocus="">
-                                </div>
-                                <div class="form-group">
-                                    <input class="form-control" placeholder="Password" name="password" type="password" value="">
-                                </div>
-                                <a href="/login"><button type="submit" class="btn btn-default">Login</button></a>
-                            </fieldset>
-                        </form>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <script type="text/javascript" src="http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js"></script>
-	</body>
-</html>

+ 0 - 3
Resources/test.json

@@ -1,3 +0,0 @@
-{
-    "test" : "test"
-}

+ 119 - 58
Sources/DemoServer.swift

@@ -7,55 +7,89 @@
 
 import Foundation
 
-public func demoServer(publicDir: String) -> HttpServer {
+
+public func demoServer(_ publicDir: String) -> HttpServer {
     
     print(publicDir)
     
     let server = HttpServer()
     
-    server["/public/:path"] = HttpHandlers.shareFilesFromDirectory(publicDir)
-    server["/public/"] = HttpHandlers.shareFilesFromDirectory(publicDir)    // needed to serve index file at root level
+    server["/public/:path"] = shareFilesFromDirectory(publicDir)
 
-    server["/files/:path"] = HttpHandlers.directoryBrowser("/")
+    server["/files/:path"] = directoryBrowser("/")
 
-    server["/"] = { r in
-        var listPage = "Available services:<br><ul>"
-        for services in server.routes {
-            if services.isEmpty {
-                listPage += "<li><a href=\"/\">/</a></li>"
-            } else {
-                listPage += "<li><a href=\"\(services)\">\(services)</a></li>"
+    server["/"] = scopes {
+        html {
+            body {
+                ul(server.routes) { service in
+                    li {
+                        a { href = service; inner = service }
+                    }
+                }
             }
         }
-        listPage += "</ul>"
-        return .OK(.Html(listPage))
     }
     
-    server["/magic"] = { .OK(.Html("You asked for " + $0.path)) }
+    server["/magic"] = { .ok(.html("You asked for " + $0.path)) }
     
     server["/test/:param1/:param2"] = { r in
-        var headersInfo = ""
-        for (name, value) in r.headers {
-            headersInfo += "\(name) : \(value)<br>"
-        }
-        var queryParamsInfo = ""
-        for (name, value) in r.queryParams {
-            queryParamsInfo += "\(name) : \(value)<br>"
-        }
-        var pathParamsInfo = ""
-        for token in r.params {
-            pathParamsInfo += "\(token.0) : \(token.1)<br>"
-        }
-        return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.path)<h3>Method:</h3>\(r.method)<h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
+        scopes {
+            html {
+                body {
+                    h3 { inner = "Address: \(r.address)" }
+                    h3 { inner = "Url: \(r.path)" }
+                    h3 { inner = "Method: \(r.method)" }
+                    
+                    h3 { inner = "Query:" }
+                    
+                    table(r.queryParams) { param in
+                        tr {
+                            td { inner = param.0 }
+                            td { inner = param.1 }
+                        }
+                    }
+                    
+                    h3 { inner = "Headers:" }
+                    
+                    table(r.headers) { header in
+                        tr {
+                            td { inner = header.0 }
+                            td { inner = header.1 }
+                        }
+                    }
+                    
+                    h3 { inner = "Route params:" }
+                    
+                    table(r.params) { param in
+                        tr {
+                            td { inner = param.0 }
+                            td { inner = param.1 }
+                        }
+                    }
+                }
+            }
+        }(r)
     }
     
-    server.GET["/upload"] = { r in
-        if let html = NSData(contentsOfFile:"\(publicDir)/file.html") {
-            var array = [UInt8](count: html.length, repeatedValue: 0)
-            html.getBytes(&array, length: html.length)
-            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+    server.GET["/upload"] = scopes {
+        html {
+            body {
+                form {
+                    method = "POST"
+                    action = "/upload"
+                    enctype = "multipart/form-data"
+                    
+                    input { name = "my_file1"; type = "file" }
+                    input { name = "my_file2"; type = "file" }
+                    input { name = "my_file3"; type = "file" }
+                    
+                    button {
+                        type = "submit"
+                        inner = "Upload"
+                    }
+                }
+            }
         }
-        return .NotFound
     }
     
     server.POST["/upload"] = { r in
@@ -63,72 +97,99 @@ public func demoServer(publicDir: String) -> HttpServer {
         for multipart in r.parseMultiPartFormData() {
             response += "Name: \(multipart.name) File name: \(multipart.fileName) Size: \(multipart.body.count)<br>"
         }
-        return HttpResponse.OK(.Html(response))
+        return HttpResponse.ok(.html(response))
     }
     
-    server.GET["/login"] = { r in
-        if let html = NSData(contentsOfFile:"\(publicDir)/login.html") {
-            var array = [UInt8](count: html.length, repeatedValue: 0)
-            html.getBytes(&array, length: html.length)
-            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+    server.GET["/login"] = scopes {
+        html {
+            head {
+                script { src = "http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js" }
+                stylesheet { href = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" }
+            }
+            body {
+                h3 { inner = "Sign In" }
+                
+                form {
+                    method = "POST"
+                    action = "/login"
+                    
+                    fieldset {
+                        input { placeholder = "E-mail"; name = "email"; type = "email"; autofocus = "" }
+                        input { placeholder = "Password"; name = "password"; type = "password"; autofocus = "" }
+                        a {
+                            href = "/login"
+                            button {
+                                type = "submit"
+                                inner = "Login"
+                            }
+                        }
+                    }
+                    
+                }
+                javascript {
+                    src = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js"
+                }
+            }
         }
-        return .NotFound
     }
     
     server.POST["/login"] = { r in
         let formFields = r.parseUrlencodedForm()
-        return HttpResponse.OK(.Html(formFields.map({ "\($0.0) = \($0.1)" }).joinWithSeparator("<br>")))
+        return HttpResponse.ok(.html(formFields.map({ "\($0.0) = \($0.1)" }).joined(separator: "<br>")))
     }
     
-    server["/demo"] = { r in
-        return .OK(.Html("<center><h2>Hello Swift</h2><img src=\"https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png\"/><br></center>"))
+    server["/demo"] = scopes {
+        html {
+            body {
+                center {
+                    h2 { inner = "Hello Swift" }
+                    img { src = "https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png" }
+                }
+            }
+        }
     }
     
     server["/raw"] = { r in
-        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], { $0.write([UInt8]("test".utf8)) })
-    }
-    
-    server["/json"] = { r in
-        let jsonObject: NSDictionary = [NSString(string: "foo"): NSNumber(int: 3), NSString(string: "bar"): NSString(string: "baz")] 
-        return .OK(.Json(jsonObject))
+        return HttpResponse.raw(200, "OK", ["XXX-Custom-Header": "value"], { try $0.write([UInt8]("test".utf8)) })
     }
     
     server["/redirect"] = { r in
-        return .MovedPermanently("http://www.google.com")
+        return .movedPermanently("http://www.google.com")
     }
 
     server["/long"] = { r in
         var longResponse = ""
         for k in 0..<1000 { longResponse += "(\(k)),->" }
-        return .OK(.Html(longResponse))
+        return .ok(.html(longResponse))
     }
     
     server["/wildcard/*/test/*/:param"] = { r in
-        return .OK(.Html(r.path))
+        return .ok(.html(r.path))
     }
     
     server["/stream"] = { r in
-        return HttpResponse.RAW(200, "OK", nil, { w in
+        return HttpResponse.raw(200, "OK", nil, { w in
             for i in 0...100 {
-                w.write([UInt8]("[chunk \(i)]".utf8))
+                try w.write([UInt8]("[chunk \(i)]".utf8))
             }
         })
     }
     
-    server["/websocket-echo"] = HttpHandlers.websocket({ (session, text) in
+    server["/websocket-echo"] = websocket({ (session, text) in
         session.writeText(text)
-    }, { (session, binary) in
+        }, { (session, binary) in
         session.writeBinary(binary)
     })
     
     server.notFoundHandler = { r in
-        return .MovedPermanently("https://github.com/404")
+        return .movedPermanently("https://github.com/404")
     }
     
     server.middleware.append { r in
-        print("Middleware:\(r.method) \(r.path)")
+        print("Middleware: \(r.address) -> \(r.method) -> \(r.path)")
         return nil
     }
     
     return server
 }
+    

+ 15 - 0
Sources/Errno.swift

@@ -0,0 +1,15 @@
+//
+//  Errno.swift
+//  Swifter
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class Errno {
+    
+    public class func description() -> String {
+        return String(cString: UnsafePointer(strerror(errno)))
+    }
+}

+ 0 - 125
Sources/File.swift

@@ -1,125 +0,0 @@
-//
-//  File.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
-
-public enum FileError: ErrorType {
-    case OpenFailed(String)
-    case WriteFailed(String)
-    case ReadFailed(String)
-    case SeekFailed(String)
-    case GetCurrentWorkingDirectoryFailed(String)
-}
-
-public class File {
-    
-    public static func openNewForWriting(path: String) throws -> File {
-        return try openFileForMode(path, "wb")
-    }
-    
-    public static func openForReading(path: String) throws -> File {
-        return try openFileForMode(path, "rb")
-    }
-    
-    public static func openForWritingAndReading(path: String) throws -> File {
-        return try openFileForMode(path, "r+b")
-    }
-    
-    public static func openFileForMode(path: String, _ mode: String) throws -> File {
-        let file = fopen(path.withCString({ $0 }), mode.withCString({ $0 }))
-        guard file != nil else {
-            throw FileError.OpenFailed(descriptionOfLastError())
-        }
-        return File(file)
-    }
-    
-    public static func currentWorkingDirectory() throws -> String {
-        let path = getcwd(nil, 0)
-        if path == nil {
-            throw FileError.GetCurrentWorkingDirectoryFailed(descriptionOfLastError())
-        }
-        guard let result = String.fromCString(path) else {
-            throw FileError.GetCurrentWorkingDirectoryFailed("Could not convert getcwd(...)'s result to String.")
-        }
-        return result
-    }
-    
-    private let pointer: UnsafeMutablePointer<FILE>
-    
-    public init(_ pointer: UnsafeMutablePointer<FILE>) {
-        self.pointer = pointer
-    }
-    
-    public func close() -> Void {
-        fclose(pointer)
-    }
-    
-    public func read(inout data: [UInt8]) throws -> Int {
-        if data.count <= 0 {
-            return data.count
-        }
-        let count = fread(&data, 1, data.count, self.pointer)
-        if count == data.count {
-            return count
-        }
-        if feof(self.pointer) != 0 {
-            return count
-        }
-        if ferror(self.pointer) != 0 {
-            throw FileError.ReadFailed(File.descriptionOfLastError())
-        }
-        throw FileError.ReadFailed("Unknown file read error occured.")
-    }
-
-    public func write(data: [UInt8]) throws -> Void {
-        if data.count <= 0 {
-            return
-        }
-        try data.withUnsafeBufferPointer {
-            if fwrite($0.baseAddress, 1, data.count, self.pointer) != data.count {
-                throw FileError.WriteFailed(File.descriptionOfLastError())
-            }
-        }
-    }
-    
-    public func seek(offset: Int) throws -> Void {
-        if fseek(self.pointer, offset, SEEK_SET) != 0 {
-            throw FileError.SeekFailed(File.descriptionOfLastError())
-        }
-    }
-    
-    private static func descriptionOfLastError() -> String {
-        return String.fromCString(UnsafePointer(strerror(errno))) ?? "Error: \(errno)"
-    }
-}
-
-extension File {
-    
-    public static func withNewFileOpenedForWriting<Result>(path: String, _ f: File throws -> Result) throws -> Result {
-        return try withFileOpenedForMode(path, mode: "wb", f)
-    }
-    
-    public static func withFileOpenedForReading<Result>(path: String, _ f: File throws -> Result) throws -> Result {
-        return try withFileOpenedForMode(path, mode: "rb", f)
-    }
-    
-    public static func withFileOpenedForWritingAndReading<Result>(path: String, _ f: File throws -> Result) throws -> Result {
-        return try withFileOpenedForMode(path, mode: "r+b", f)
-    }
-    
-    public static func withFileOpenedForMode<Result>(path: String, mode: String, _ f: File throws -> Result) throws -> Result {
-        let file = try File.openFileForMode(path, mode)
-        defer {
-            file.close()
-        }
-        return try f(file)
-    }
-}

+ 88 - 0
Sources/Files.swift

@@ -0,0 +1,88 @@
+//
+//  HttpHandlers+Files.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public func shareFile(_ path: String) -> ((HttpRequest) -> HttpResponse) {
+    return { r in
+        if let file = try? path.openForReading() {
+            return .raw(200, "OK", [:], { writer in
+                try? writer.write(file)
+                file.close()
+            })
+        }
+        return .notFound
+    }
+}
+
+public func shareFilesFromDirectory(_ directoryPath: String, defaults: [String] = ["index.html", "default.html"]) -> ((HttpRequest) -> HttpResponse) {
+    return { r in
+        guard let fileRelativePath = r.params.first else {
+            return .notFound
+        }
+        if fileRelativePath.value.isEmpty {
+            for path in defaults {
+                if let file = try? (directoryPath + String.pathSeparator + path).openForReading() {
+                    return .raw(200, "OK", [:], { writer in
+                        try? writer.write(file)
+                        file.close()
+                    })
+                }
+            }
+        }
+        if let file = try? (directoryPath + String.pathSeparator + fileRelativePath.value).openForReading() {
+            return .raw(200, "OK", [:], { writer in
+                try? writer.write(file)
+                file.close()
+            })
+        }
+        return .notFound
+    }
+}
+
+public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) {
+    return { r in
+        guard let (_, value) = r.params.first else {
+            return HttpResponse.notFound
+        }
+        let filePath = dir + String.pathSeparator + value
+        do {
+            guard try filePath.exists() else {
+                return .notFound
+            }
+            if try filePath.directory() {
+                let files = try filePath.files()
+                return scopes {
+                    html {
+                        body {
+                            table(files) { file in
+                                tr {
+                                    td {
+                                        a {
+                                            href = r.path + "/" + file
+                                            inner = file
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    }(r)
+            } else {
+                guard let file = try? filePath.openForReading() else {
+                    return .notFound
+                }
+                return .raw(200, "OK", [:], { writer in
+                    try? writer.write(file)
+                    file.close()
+                })
+            }
+        } catch {
+            return HttpResponse.internalServerError
+        }
+    }
+}

+ 0 - 152
Sources/HttpHandlers+Files.swift

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

+ 0 - 171
Sources/HttpHandlers+WebSockets.swift

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

+ 0 - 12
Sources/HttpHandlers.swift

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

+ 52 - 29
Sources/HttpParser.swift

@@ -5,13 +5,9 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
-enum HttpParserError: ErrorType {
+enum HttpParserError: Error {
     case InvalidStatusLine(String)
 }
 
@@ -19,9 +15,9 @@ public class HttpParser {
     
     public init() { }
     
-    public func readHttpRequest(socket: Socket) throws -> HttpRequest {
+    public func readHttpRequest(_ socket: Socket) throws -> HttpRequest {
         let statusLine = try socket.readLine()
-        let statusLineTokens = statusLine.split(" ")
+        let statusLineTokens = statusLine.components(separatedBy: " ")
         if statusLineTokens.count < 3 {
             throw HttpParserError.InvalidStatusLine(statusLine)
         }
@@ -36,42 +32,69 @@ public class HttpParser {
         return request
     }
     
-    private func extractQueryParams(url: String) -> [(String, String)] {
-        guard let query = url.split("?").last else {
+    private func extractQueryParams(_ url: String) -> [(String, String)] {
+        guard let questionMark = url.characters.index(of: "?") else {
             return []
         }
-        return query.split("&").reduce([(String, String)]()) { (c, s) -> [(String, String)] in
-            let tokens = s.split(1, separator: "=")
-            if let name = tokens.first, value = tokens.last {
-                return c + [(name.removePercentEncoding(), value.removePercentEncoding())]
-            }
-            return c
+        let queryStart = url.characters.index(after: questionMark)
+        guard url.endIndex > queryStart else {
+            return []
+        }
+        let query = String(url.characters[queryStart..<url.endIndex])
+        return query.components(separatedBy: "&")
+            .reduce([(String, String)]()) { (c, s) -> [(String, String)] in
+                guard let nameEndIndex = s.characters.index(of: "=") else {
+                    return c
+                }
+                guard let name = String(s.characters[s.startIndex..<nameEndIndex]).removingPercentEncoding else {
+                    return c
+                }
+                let valueStartIndex = s.index(nameEndIndex, offsetBy: 1)
+                guard valueStartIndex < s.endIndex else {
+                    return c + [(name, "")]
+                }
+                guard let value = String(s.characters[valueStartIndex..<s.endIndex]).removingPercentEncoding else {
+                    return c + [(name, "")]
+                }
+                return c + [(name, value)]
         }
+        
+        
+//        let tokens = url.components(separatedBy: "?")
+//        guard let query = tokens.last, tokens.count >= 2 else {
+//            return []
+//        }
+//        return query.components(separatedBy: "&").reduce([(String, String)]()) { (c, s) -> [(String, String)] in
+//            let tokens = s.components(separatedBy: "=")
+//            let name = tokens.first?.removingPercentEncoding
+//            let value = tokens.count > 1 ? (tokens.last?.removingPercentEncoding ?? "") : ""
+//            if let nameFound = name {
+//                return c + [(nameFound, value)]
+//            }
+//            return c
+//        }
     }
     
-    private func readBody(socket: Socket, size: Int) throws -> [UInt8] {
+    private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] {
         var body = [UInt8]()
         for _ in 0..<size { body.append(try socket.read()) }
         return body
     }
     
-    private func readHeaders(socket: Socket) throws -> [String: String] {
+    private func readHeaders(_ socket: Socket) throws -> [String: String] {
         var headers = [String: String]()
-        repeat {
-            let headerLine = try socket.readLine()
-            if headerLine.isEmpty {
-                return headers
+        while case let headerLine = try socket.readLine() , !headerLine.isEmpty {
+            let headerTokens = headerLine.components(separatedBy: ":")
+            if let name = headerTokens.first, let value = headerTokens.last {
+                headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces)
             }
-            let headerTokens = headerLine.split(1, separator: ":")
-            if let name = headerTokens.first, value = headerTokens.last {
-                headers[name.lowercaseString] = value.trim()
-            }
-        } while true
+        }
+        return headers
     }
     
-    func supportsKeepAlive(headers: [String: String]) -> Bool {
+    func supportsKeepAlive(_ headers: [String: String]) -> Bool {
         if let value = headers["connection"] {
-            return "keep-alive" == value.trim()
+            return "keep-alive" == value.trimmingCharacters(in: .whitespaces)
         }
         return false
     }

+ 44 - 37
Sources/HttpRequest.swift

@@ -5,11 +5,7 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
 public class HttpRequest {
     
@@ -21,19 +17,30 @@ public class HttpRequest {
     public var address: String? = ""
     public var params: [String: String] = [:]
     
+    public func hasTokenForHeader(_ headerName: String, token: String) -> Bool {
+        guard let headerValue = headers[headerName] else {
+            return false
+        }
+        return headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).count > 0
+    }
+    
     public func parseUrlencodedForm() -> [(String, String)] {
         guard let contentTypeHeader = headers["content-type"] else {
             return []
         }
-        let contentTypeHeaderTokens = contentTypeHeader.split(";").map { $0.trim() }
-        guard let contentType = contentTypeHeaderTokens.first where contentType == "application/x-www-form-urlencoded" else {
+        let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
+        guard let contentType = contentTypeHeaderTokens.first, contentType == "application/x-www-form-urlencoded" else {
             return []
         }
-        return String.fromUInt8(body).split("&").map { param -> (String, String) in
-            let tokens = param.split("=")
-            if let name = tokens.first, value = tokens.last where tokens.count == 2 {
-                return (name.replace("+", " ").removePercentEncoding(),
-                        value.replace("+", " ").removePercentEncoding())
+        guard let utf8String = String(bytes: body, encoding: .utf8) else {
+            // Consider to throw an exception here (examine the encoding from headers).
+            return []
+        }
+        return utf8String.components(separatedBy: "&").map { param -> (String, String) in
+            let tokens = param.components(separatedBy: "=")
+            if let name = tokens.first?.removingPercentEncoding, let value = tokens.last?.removingPercentEncoding, tokens.count == 2 {
+                return (name.replacingOccurrences(of: "+", with: " "),
+                        value.replacingOccurrences(of: "+", with: " "))
             }
             return ("","")
         }
@@ -52,20 +59,20 @@ public class HttpRequest {
             return valueFor("content-disposition", parameter: "filename")?.unquote()
         }
         
-        private func valueFor(headerName: String, parameter: String) -> String? {
+        private func valueFor(_ headerName: String, parameter: String) -> String? {
             return headers.reduce([String]()) { (combined, header: (key: String, value: String)) -> [String] in
                 guard header.key == headerName else {
                     return combined
                 }
-                let headerValueParams = header.value.split(";").map { $0.trim() }
-                return headerValueParams.reduce(combined, combine: { (results, token) -> [String] in
-                    let parameterTokens = token.split(1, separator: "=")
+                let headerValueParams = header.value.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
+                return headerValueParams.reduce(combined, { (results, token) -> [String] in
+                    let parameterTokens = token.components(separatedBy: "=")
                     if parameterTokens.first == parameter, let value = parameterTokens.last {
                         return results + [value]
                     }
                     return results
                 })
-            }.first
+                }.first
         }
     }
     
@@ -73,25 +80,25 @@ public class HttpRequest {
         guard let contentTypeHeader = headers["content-type"] else {
             return []
         }
-        let contentTypeHeaderTokens = contentTypeHeader.split(";").map { $0.trim() }
-        guard let contentType = contentTypeHeaderTokens.first where contentType == "multipart/form-data" else {
+        let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
+        guard let contentType = contentTypeHeaderTokens.first, contentType == "multipart/form-data" else {
             return []
         }
         var boundary: String? = nil
         contentTypeHeaderTokens.forEach({
-            let tokens = $0.split("=")
-            if let key = tokens.first where key == "boundary" && tokens.count == 2 {
+            let tokens = $0.components(separatedBy: "=")
+            if let key = tokens.first, key == "boundary" && tokens.count == 2 {
                 boundary = tokens.last
             }
         })
-        if let boundary = boundary where boundary.utf8.count > 0 {
+        if let boundary = boundary, boundary.utf8.count > 0 {
             return parseMultiPartFormData(body, boundary: "--\(boundary)")
         }
         return []
     }
     
-    private func parseMultiPartFormData(data: [UInt8], boundary: String) -> [MultiPart] {
-        var generator = data.generate()
+    private func parseMultiPartFormData(_ data: [UInt8], boundary: String) -> [MultiPart] {
+        var generator = data.makeIterator()
         var result = [MultiPart]()
         while let part = nextMultiPart(&generator, boundary: boundary, isFirst: result.isEmpty) {
             result.append(part)
@@ -99,19 +106,19 @@ public class HttpRequest {
         return result
     }
     
-    private func nextMultiPart(inout generator: IndexingGenerator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? {
+    private func nextMultiPart(_ generator: inout IndexingIterator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? {
         if isFirst {
-            guard nextMultiPartLine(&generator) == boundary else {
+            guard nextUTF8MultiPartLine(&generator) == boundary else {
                 return nil
             }
         } else {
-            nextMultiPartLine(&generator)
+            let /* ignore */ _ = nextUTF8MultiPartLine(&generator)
         }
         var headers = [String: String]()
-        while let line = nextMultiPartLine(&generator) where !line.isEmpty {
-            let tokens = line.split(":")
-            if let name = tokens.first, value = tokens.last where tokens.count == 2 {
-                headers[name.lowercaseString] = value.trim()
+        while let line = nextUTF8MultiPartLine(&generator), !line.isEmpty {
+            let tokens = line.components(separatedBy: ":")
+            if let name = tokens.first, let value = tokens.last, tokens.count == 2 {
+                headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces)
             }
         }
         guard let body = nextMultiPartBody(&generator, boundary: boundary) else {
@@ -120,23 +127,23 @@ public class HttpRequest {
         return MultiPart(headers: headers, body: body)
     }
     
-    private func nextMultiPartLine(inout generator: IndexingGenerator<[UInt8]>) -> String? {
-        var result = String()
+    private func nextUTF8MultiPartLine(_ generator: inout IndexingIterator<[UInt8]>) -> String? {
+        var temp = [UInt8]()
         while let value = generator.next() {
             if value > HttpRequest.CR {
-                result.append(Character(UnicodeScalar(value)))
+                temp.append(value)
             }
             if value == HttpRequest.NL {
                 break
             }
         }
-        return result
+        return String(bytes: temp, encoding: String.Encoding.utf8)
     }
     
     static let CR = UInt8(13)
     static let NL = UInt8(10)
     
-    private func nextMultiPartBody(inout generator: IndexingGenerator<[UInt8]>, boundary: String) -> [UInt8]? {
+    private func nextMultiPartBody(_ generator: inout IndexingIterator<[UInt8]>, boundary: String) -> [UInt8]? {
         var body = [UInt8]()
         let boundaryArray = [UInt8](boundary.utf8)
         var matchOffset = 0;
@@ -144,7 +151,7 @@ public class HttpRequest {
             matchOffset = ( x == boundaryArray[matchOffset] ? matchOffset + 1 : 0 )
             body.append(x)
             if matchOffset == boundaryArray.count {
-                body.removeRange(Range<Int>(body.count-matchOffset ..< body.count))
+                body.removeSubrange(CountableRange<Int>(body.count-matchOffset ..< body.count))
                 if body.last == HttpRequest.NL {
                     body.removeLast()
                     if body.last == HttpRequest.CR {

+ 68 - 70
Sources/HttpResponse.swift

@@ -5,70 +5,68 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
-public enum SerializationError: ErrorType {
-    case InvalidObject
-    case NotSupported
+public enum SerializationError: Error {
+    case invalidObject
+    case notSupported
 }
 
 public protocol HttpResponseBodyWriter {
-    func write(data: [UInt8])
-    func write(data: ArraySlice<UInt8>)
+    func write(_ file: String.File) throws
+    func write(_ data: [UInt8]) throws
+    func write(_ data: ArraySlice<UInt8>) throws
+    func write(_ data: NSData) throws
+    func write(_ data: Data) throws
 }
 
 public enum HttpResponseBody {
     
-    case Json(AnyObject)
-    case Html(String)
-    case Text(String)
-    case Custom(Any, (Any) throws -> String)
+    case json(AnyObject)
+    case html(String)
+    case text(String)
+    case custom(Any, (Any) throws -> String)
     
-    func content() -> (Int, (HttpResponseBodyWriter throws -> Void)?) {
+    func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) {
         do {
             switch self {
-            case .Json(let object):
+            case .json(let object):
                 #if os(Linux)
                     let data = [UInt8]("Not ready for Linux.".utf8)
                     return (data.count, {
-                        $0.write(data)
+                        try $0.write(data)
                     })
                 #else
-                    guard NSJSONSerialization.isValidJSONObject(object) else {
-                        throw SerializationError.InvalidObject
+                    guard JSONSerialization.isValidJSONObject(object) else {
+                        throw SerializationError.invalidObject
                     }
-                    let json = try NSJSONSerialization.dataWithJSONObject(object, options: NSJSONWritingOptions.PrettyPrinted)
-                    let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
+                    let data = try JSONSerialization.data(withJSONObject: object)
                     return (data.count, {
-                        $0.write(data)
+                        try $0.write(data)
                     })
                 #endif
-            case .Text(let body):
+            case .text(let body):
                 let data = [UInt8](body.utf8)
                 return (data.count, {
-                    $0.write(data)
+                    try $0.write(data)
                 })
-            case .Html(let body):
+            case .html(let body):
                 let serialised = "<html><meta charset=\"UTF-8\"><body>\(body)</body></html>"
                 let data = [UInt8](serialised.utf8)
                 return (data.count, {
-                    $0.write(data)
+                    try $0.write(data)
                 })
-            case .Custom(let object, let closure):
+            case .custom(let object, let closure):
                 let serialised = try closure(object)
                 let data = [UInt8](serialised.utf8)
                 return (data.count, {
-                    $0.write(data)
+                    try $0.write(data)
                 })
             }
         } catch {
             let data = [UInt8]("Serialisation error: \(error)".utf8)
             return (data.count, {
-                $0.write(data)
+                try $0.write(data)
             })
         }
     }
@@ -76,61 +74,61 @@ public enum HttpResponseBody {
 
 public enum HttpResponse {
     
-    case SwitchProtocols([String: String], Socket -> Void)
-    case OK(HttpResponseBody), Created, Accepted
-    case MovedPermanently(String)
-    case BadRequest(HttpResponseBody?), Unauthorized, Forbidden, NotFound
-    case InternalServerError
-    case RAW(Int, String, [String:String]?, (HttpResponseBodyWriter -> Void)? )
-    
+    case switchProtocols([String: String], (Socket) -> Void)
+    case ok(HttpResponseBody), created, accepted
+    case movedPermanently(String)
+    case badRequest(HttpResponseBody?), unauthorized, forbidden, notFound
+    case internalServerError
+    case raw(Int, String, [String:String]?, ((HttpResponseBodyWriter) throws -> Void)? )
+
     func statusCode() -> Int {
         switch self {
-        case .SwitchProtocols(_, _)   : return 101
-        case .OK(_)                   : return 200
-        case .Created                 : return 201
-        case .Accepted                : return 202
-        case .MovedPermanently        : return 301
-        case .BadRequest(_)           : return 400
-        case .Unauthorized            : return 401
-        case .Forbidden               : return 403
-        case .NotFound                : return 404
-        case .InternalServerError     : return 500
-        case .RAW(let code, _ , _, _) : return code
+        case .switchProtocols(_, _)   : return 101
+        case .ok(_)                   : return 200
+        case .created                 : return 201
+        case .accepted                : return 202
+        case .movedPermanently        : return 301
+        case .badRequest(_)           : return 400
+        case .unauthorized            : return 401
+        case .forbidden               : return 403
+        case .notFound                : return 404
+        case .internalServerError     : return 500
+        case .raw(let code, _ , _, _) : return code
         }
     }
     
     func reasonPhrase() -> String {
         switch self {
-        case .SwitchProtocols(_, _)    : return "Switching Protocols"
-        case .OK(_)                    : return "OK"
-        case .Created                  : return "Created"
-        case .Accepted                 : return "Accepted"
-        case .MovedPermanently         : return "Moved Permanently"
-        case .BadRequest(_)            : return "Bad Request"
-        case .Unauthorized             : return "Unauthorized"
-        case .Forbidden                : return "Forbidden"
-        case .NotFound                 : return "Not Found"
-        case .InternalServerError      : return "Internal Server Error"
-        case .RAW(_, let phrase, _, _) : return phrase
+        case .switchProtocols(_, _)    : return "Switching Protocols"
+        case .ok(_)                    : return "OK"
+        case .created                  : return "Created"
+        case .accepted                 : return "Accepted"
+        case .movedPermanently         : return "Moved Permanently"
+        case .badRequest(_)            : return "Bad Request"
+        case .unauthorized             : return "Unauthorized"
+        case .forbidden                : return "Forbidden"
+        case .notFound                 : return "Not Found"
+        case .internalServerError      : return "Internal Server Error"
+        case .raw(_, let phrase, _, _) : return phrase
         }
     }
     
     func headers() -> [String: String] {
         var headers = ["Server" : "Swifter \(HttpServer.VERSION)"]
         switch self {
-        case .SwitchProtocols(let switchHeaders, _):
+        case .switchProtocols(let switchHeaders, _):
             for (key, value) in switchHeaders {
                 headers[key] = value
             }
-        case .OK(let body):
+        case .ok(let body):
             switch body {
-            case .Json(_)   : headers["Content-Type"] = "application/json"
-            case .Html(_)   : headers["Content-Type"] = "text/html"
+            case .json(_)   : headers["Content-Type"] = "application/json"
+            case .html(_)   : headers["Content-Type"] = "text/html"
             default:break
             }
-        case .MovedPermanently(let location):
+        case .movedPermanently(let location):
             headers["Location"] = location
-        case .RAW(_, _, let rawHeaders, _):
+        case .raw(_, _, let rawHeaders, _):
             if let rawHeaders = rawHeaders {
                 for (k, v) in rawHeaders {
                     headers.updateValue(v, forKey: k)
@@ -141,18 +139,18 @@ public enum HttpResponse {
         return headers
     }
     
-    func content() -> (length: Int, write: (HttpResponseBodyWriter throws -> Void)?) {
+    func content() -> (length: Int, write: ((HttpResponseBodyWriter) throws -> Void)?) {
         switch self {
-        case .OK(let body)             : return body.content()
-        case .BadRequest(let body)     : return body?.content() ?? (-1, nil)
-        case .RAW(_, _, _, let writer) : return (-1, writer)
+        case .ok(let body)             : return body.content()
+        case .badRequest(let body)     : return body?.content() ?? (-1, nil)
+        case .raw(_, _, _, let writer) : return (-1, writer)
         default                        : return (-1, nil)
         }
     }
     
-    func socketSession() -> (Socket -> Void)?  {
+    func socketSession() -> ((Socket) -> Void)?  {
         switch self {
-        case SwitchProtocols(_, let handler) : return handler
+        case .switchProtocols(_, let handler) : return handler
         default: return nil
         }
     }

+ 50 - 27
Sources/HttpRouter.swift

@@ -5,17 +5,17 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
-public class HttpRouter {
+
+open class HttpRouter {
+    
+    public init() {
+    }
     
     private class Node {
         var nodes = [String: Node]()
-        var handler: (HttpRequest -> HttpResponse)? = nil
+        var handler: ((HttpRequest) -> HttpResponse)? = nil
     }
     
     private var rootNode = Node()
@@ -23,44 +23,44 @@ public class HttpRouter {
     public func routes() -> [String] {
         var routes = [String]()
         for (_, child) in rootNode.nodes {
-            routes.appendContentsOf(routesForNode(child));
+            routes.append(contentsOf: routesForNode(child));
         }
         return routes
     }
     
-    private func routesForNode(node: Node, prefix: String = "") -> [String] {
+    private func routesForNode(_ node: Node, prefix: String = "") -> [String] {
         var result = [String]()
         if let _ = node.handler {
             result.append(prefix)
         }
         for (key, child) in node.nodes {
-            result.appendContentsOf(routesForNode(child, prefix: prefix + "/" + key));
+            result.append(contentsOf: routesForNode(child, prefix: prefix + "/" + key));
         }
         return result
     }
     
-    public func register(method: String?, path: String, handler: (HttpRequest -> HttpResponse)?) {
+    public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) {
         var pathSegments = stripQuery(path).split("/")
         if let method = method {
-            pathSegments.insert(method, atIndex: 0)
+            pathSegments.insert(method, at: 0)
         } else {
-            pathSegments.insert("*", atIndex: 0)
+            pathSegments.insert("*", at: 0)
         }
-        var pathSegmentsGenerator = pathSegments.generate()
+        var pathSegmentsGenerator = pathSegments.makeIterator()
         inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler
     }
     
-    public func route(method: String?, path: String) -> ([String: String], HttpRequest -> HttpResponse)? {
+    public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? {
         if let method = method {
             let pathSegments = (method + "/" + stripQuery(path)).split("/")
-            var pathSegmentsGenerator = pathSegments.generate()
+            var pathSegmentsGenerator = pathSegments.makeIterator()
             var params = [String:String]()
             if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
                 return (params, handler)
             }
         }
         let pathSegments = ("*/" + stripQuery(path)).split("/")
-        var pathSegmentsGenerator = pathSegments.generate()
+        var pathSegmentsGenerator = pathSegments.makeIterator()
         var params = [String:String]()
         if let handler = findHandler(&rootNode, params: &params, generator: &pathSegmentsGenerator) {
             return (params, handler)
@@ -68,7 +68,7 @@ public class HttpRouter {
         return nil
     }
     
-    private func inflate(inout node: Node, inout generator: IndexingGenerator<[String]>) -> Node {
+    private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node {
         if let pathSegment = generator.next() {
             if let _ = node.nodes[pathSegment] {
                 return inflate(&node.nodes[pathSegment]!, generator: &generator)
@@ -80,8 +80,15 @@ public class HttpRouter {
         return node
     }
     
-    private func findHandler(inout node: Node, inout params: [String: String], inout generator: IndexingGenerator<[String]>) -> (HttpRequest -> HttpResponse)? {
+    private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? {
         guard let pathToken = generator.next() else {
+            // if it's the last element of the requested URL, check if there is a pattern with variable tail.
+            if let variableNode = node.nodes.filter({ $0.0.characters.first == ":" }).first {
+                if variableNode.value.nodes.isEmpty {
+                    params[variableNode.0] = ""
+                    return variableNode.value.handler
+                }
+            }
             return node.handler
         }
         let variableNodes = node.nodes.filter { $0.0.characters.first == ":" }
@@ -89,8 +96,8 @@ public class HttpRouter {
             if variableNode.1.nodes.count == 0 {
                 // if it's the last element of the pattern and it's a variable, stop the search and
                 // append a tail as a value for the variable.
-                let tail = generator.joinWithSeparator("/")
-                if tail.utf8.count > 0 {
+                let tail = generator.joined(separator: "/")
+                if tail.characters.count > 0 {
                     params[variableNode.0] = pathToken + "/" + tail
                 } else {
                     params[variableNode.0] = pathToken
@@ -100,19 +107,35 @@ 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)
+        }
+        if let startStarNode = node.nodes["**"] {
+            let startStarNodeKeys = startStarNode.nodes.keys
+            while let pathToken = generator.next() {
+                if startStarNodeKeys.contains(pathToken) {
+                    return findHandler(&startStarNode.nodes[pathToken]!, params: &params, generator: &generator)
+                }
+            }
         }
         return nil
     }
     
-    private func stripQuery(path: String) -> String {
-        if let path = path.split("?").first {
+    private func stripQuery(_ path: String) -> String {
+        if let path = path.components(separatedBy: "?").first {
             return path
         }
         return path
     }
 }
+
+extension String {
+    
+    public func split(_ separator: Character) -> [String] {
+        return self.characters.split { $0 == separator }.map(String.init)
+    }
+    
+}

+ 6 - 10
Sources/HttpServer.swift

@@ -5,15 +5,11 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
 public class HttpServer: HttpServerIO {
     
-    public static let VERSION = "1.2.0"
+    public static let VERSION = "1.3.3"
     
     private let router = HttpRouter()
     
@@ -36,7 +32,7 @@ public class HttpServer: HttpServerIO {
     public var DELETE, UPDATE, HEAD, POST, GET, PUT : MethodRoute
     public var delete, update, head, post, get, put : MethodRoute
     
-    public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
+    public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? {
         set {
             router.register(nil, path: path, handler: newValue)
         }
@@ -47,11 +43,11 @@ public class HttpServer: HttpServerIO {
         return router.routes();
     }
     
-    public var notFoundHandler: (HttpRequest -> HttpResponse)?
+    public var notFoundHandler: ((HttpRequest) -> HttpResponse)?
     
     public var middleware = Array<(HttpRequest) -> HttpResponse?>()
 
-    override public func dispatch(request: HttpRequest) -> ([String:String], HttpRequest -> HttpResponse) {
+    override public func dispatch(_ request: HttpRequest) -> ([String:String], (HttpRequest) -> HttpResponse) {
         for layer in middleware {
             if let response = layer(request) {
                 return ([:], { _ in response })
@@ -69,7 +65,7 @@ public class HttpServer: HttpServerIO {
     public struct MethodRoute {
         public let method: String
         public let router: HttpRouter
-        public subscript(path: String) -> (HttpRequest -> HttpResponse)? {
+        public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? {
             set {
                 router.register(method, path: path, handler: newValue)
             }

+ 136 - 106
Sources/HttpServerIO.swift

@@ -5,163 +5,193 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
+import Dispatch
+
+public protocol HttpServerIODelegate: class {
+    func socketConnectionReceived(_ socket: Socket)
+}
 
 public class HttpServerIO {
-    
-    private var listenSocket: Socket = Socket(socketFileDescriptor: -1)
-    private var clientSockets: Set<Socket> = []
-    private let clientSocketsLock = NSLock()
-    
-    public func start(listenPort: in_port_t = 8080, forceIPv4: Bool = false) throws {
+
+    public weak var delegate : HttpServerIODelegate?
+
+    private var socket = Socket(socketFileDescriptor: -1)
+    private var sockets = Set<Socket>()
+
+    public enum HttpServerIOState: Int32 {
+        case starting
+        case running
+        case stopping
+        case stopped
+    }
+
+    private var stateValue: Int32 = HttpServerIOState.stopped.rawValue
+
+    public private(set) var state: HttpServerIOState {
+        get {
+            return HttpServerIOState(rawValue: stateValue)!
+        }
+        set(state) {
+            #if !os(Linux)
+            OSAtomicCompareAndSwapInt(self.state.rawValue, state.rawValue, &stateValue)
+            #else
+            //TODO - hehe :)
+            self.stateValue = state.rawValue
+            #endif
+        }
+    }
+
+    public var operating: Bool { get { return self.state == .running } }
+
+    /// String representation of the IPv4 address to receive requests from.
+    /// It's only used when the server is started with `forceIPv4` option set to true.
+    /// Otherwise, `listenAddressIPv6` will be used.
+    public var listenAddressIPv4: String?
+
+    /// String representation of the IPv6 address to receive requests from.
+    /// It's only used when the server is started with `forceIPv4` option set to false.
+    /// Otherwise, `listenAddressIPv4` will be used.
+    public var listenAddressIPv6: String?
+
+    private let queue = DispatchQueue(label: "swifter.httpserverio.clientsockets")
+
+    public func port() throws -> Int {
+        return Int(try socket.port())
+    }
+
+    public func isIPv4() throws -> Bool {
+        return try socket.isIPv4()
+    }
+
+    deinit {
         stop()
-        listenSocket = try Socket.tcpSocketForListen(listenPort, forceIPv4: forceIPv4)
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
-            while let socket = try? self.listenSocket.acceptClientSocket() {
-                self.lock(self.clientSocketsLock) {
-                    self.clientSockets.insert(socket)
-                }
-                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
+    }
+
+    @available(macOS 10.10, *)
+    public func start(_ port: in_port_t = 8080, forceIPv4: Bool = false, priority: DispatchQoS.QoSClass = DispatchQoS.QoSClass.background) throws {
+        guard !self.operating else { return }
+        stop()
+        self.state = .starting
+        let address = forceIPv4 ? listenAddressIPv4 : listenAddressIPv6
+        self.socket = try Socket.tcpSocketForListen(port, forceIPv4, SOMAXCONN, address)
+        DispatchQueue.global(qos: priority).async { [weak self] in
+            guard let `self` = self else { return }
+            guard self.operating else { return }
+            while let socket = try? self.socket.acceptClientSocket() {
+                DispatchQueue.global(qos: priority).async { [weak self] in
+                    guard let `self` = self else { return }
+                    guard self.operating else { return }
+                    self.queue.async {
+                        self.sockets.insert(socket)
+                    }
                     self.handleConnection(socket)
-                    self.lock(self.clientSocketsLock) {
-                        self.clientSockets.remove(socket)
+                    self.queue.async {
+                        self.sockets.remove(socket)
                     }
-                })
+                }
             }
             self.stop()
         }
+        self.state = .running
     }
-    
+
     public func stop() {
-        listenSocket.release()
-        lock(self.clientSocketsLock) {
-            for socket in self.clientSockets {
-                socket.shutdwn()
-            }
-            self.clientSockets.removeAll(keepCapacity: true)
+        guard self.operating else { return }
+        self.state = .stopping
+        // Shutdown connected peers because they can live in 'keep-alive' or 'websocket' loops.
+        for socket in self.sockets {
+            socket.close()
         }
+        self.queue.sync {
+            self.sockets.removeAll(keepingCapacity: true)
+        }
+        socket.close()
+        self.state = .stopped
     }
-    
-    public func dispatch(request: HttpRequest) -> ([String: String], HttpRequest -> HttpResponse) {
-        return ([:], { _ in HttpResponse.NotFound })
+
+    public func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) {
+        return ([:], { _ in HttpResponse.notFound })
     }
-    
-    private func handleConnection(socket: Socket) {
-        let address = try? socket.peername()
+
+    private func handleConnection(_ socket: Socket) {
         let parser = HttpParser()
-        while let request = try? parser.readHttpRequest(socket) {
+        while self.operating, let request = try? parser.readHttpRequest(socket) {
             let request = request
+            request.address = try? socket.peername()
             let (params, handler) = self.dispatch(request)
-            request.address = address
-            request.params = params;
+            request.params = params
             let response = handler(request)
             var keepConnection = parser.supportsKeepAlive(request.headers)
             do {
-                keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection)
+                if self.operating {
+                    keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection)
+                }
             } catch {
                 print("Failed to send response: \(error)")
                 break
             }
             if let session = response.socketSession() {
+                delegate?.socketConnectionReceived(socket)
                 session(socket)
                 break
             }
             if !keepConnection { break }
         }
-        socket.release()
+        socket.close()
     }
-    
-    private func lock(handle: NSLock, closure: () -> ()) {
-        handle.lock()
-        closure()
-        handle.unlock();
-    }
-    
+
     private struct InnerWriteContext: HttpResponseBodyWriter {
+        
         let socket: Socket
-        func write(data: [UInt8]) {
-            write(ArraySlice(data))
+
+        func write(_ file: String.File) throws {
+            try socket.writeFile(file)
         }
-        func write(data: ArraySlice<UInt8>) {
-            do {
-                try socket.writeUInt8(data)
-            } catch {
-                print("\(error)")
-            }
+
+        func write(_ data: [UInt8]) throws {
+            try write(ArraySlice(data))
+        }
+
+        func write(_ data: ArraySlice<UInt8>) throws {
+            try socket.writeUInt8(data)
+        }
+
+        func write(_ data: NSData) throws {
+            try socket.writeData(data)
+        }
+
+        func write(_ data: Data) throws {
+            try socket.writeData(data)
         }
     }
-    
-    private func respond(socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
+
+    private func respond(_ socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
+        guard self.operating else { return false }
+
         try socket.writeUTF8("HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
-        
+
         let content = response.content()
-        
+
         if content.length >= 0 {
             try socket.writeUTF8("Content-Length: \(content.length)\r\n")
         }
-        
+
         if keepAlive && content.length != -1 {
             try socket.writeUTF8("Connection: keep-alive\r\n")
         }
-        
+
         for (name, value) in response.headers() {
             try socket.writeUTF8("\(name): \(value)\r\n")
         }
-        
+
         try socket.writeUTF8("\r\n")
-    
+
         if let writeClosure = content.write {
             let context = InnerWriteContext(socket: socket)
             try writeClosure(context)
         }
-        
+
         return keepAlive && content.length != -1;
     }
 }
-
-#if os(Linux)
-    
-    import Glibc
-
-    public class NSLock {
-    
-        private var mutex = pthread_mutex_t()
-	    
-        init() { pthread_mutex_init(&mutex, nil) }
-	    
-        public func lock() { pthread_mutex_lock(&mutex) }
-	    
-        public func unlock() { pthread_mutex_unlock(&mutex) }
-	    
-        deinit { pthread_mutex_destroy(&mutex) }
-    }
-
-    
-    let DISPATCH_QUEUE_PRIORITY_BACKGROUND = 0
-    
-    private class dispatch_context {
-        let block: ((Void) -> Void)
-        init(_ block: ((Void) -> Void)) {
-            self.block = block
-        }
-    }
-    
-    func dispatch_get_global_queue(queueId: Int, _ arg: Int) -> Int { return 0 }
-    
-    func dispatch_async(queueId: Int, _ block: ((Void) -> Void)) {
-        let unmanagedDispatchContext = Unmanaged.passRetained(dispatch_context(block))
-        let context = UnsafeMutablePointer<Void>(unmanagedDispatchContext.toOpaque())
-        var pthread: pthread_t = 0
-        pthread_create(&pthread, nil, { (context: UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void> in
-            let unmanaged = Unmanaged<dispatch_context>.fromOpaque(COpaquePointer(context))
-            unmanaged.takeUnretainedValue().block()
-            unmanaged.release()
-            return context
-        }, context)
-    }
-    
-#endif

+ 40 - 0
Sources/Process.swift

@@ -0,0 +1,40 @@
+//
+//  Process
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class Process {
+    
+    public static var pid: Int {
+        return Int(getpid())
+    }
+    
+    public static var tid: UInt64 {
+        #if os(Linux)
+            return UInt64(pthread_self())
+        #else
+            var tid: __uint64_t = 0
+            pthread_threadid_np(nil, &tid);
+            return UInt64(tid)
+        #endif
+    }
+    
+    private static var signalsWatchers = Array<(Int32) -> Void>()
+    private static var signalsObserved = false
+    
+    public static func watchSignals(_ callback: @escaping (Int32) -> Void) {
+        if !signalsObserved {
+            [SIGTERM, SIGHUP, SIGSTOP, SIGINT].forEach { item in
+                signal(item) {
+                    signum in Process.signalsWatchers.forEach { $0(signum) }
+                }
+            }
+            signalsObserved = true
+        }
+        signalsWatchers.append(callback)
+    }
+}

+ 870 - 0
Sources/Scopes.swift

@@ -0,0 +1,870 @@
+//
+//  HttpHandlers+Scopes.swift
+//  Swifter
+//
+//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public func scopes(_ scope: @escaping Closure) -> ((HttpRequest) -> HttpResponse) {
+    return { r in
+        ScopesBuffer[Process.tid] = ""
+        scope()
+        return .raw(200, "OK", ["Content-Type": "text/html"], {
+            try? $0.write([UInt8](("<!DOCTYPE html>"  + (ScopesBuffer[Process.tid] ?? "")).utf8))
+        })
+    }
+}
+
+public typealias Closure = (Void) -> Void
+
+public var idd: String? = nil
+public var dir: String? = nil
+public var rel: String? = nil
+public var rev: String? = nil
+public var alt: String? = nil
+public var forr: String? = nil
+public var src: String? = nil
+public var type: String? = nil
+public var href: String? = nil
+public var text: String? = nil
+public var abbr: String? = nil
+public var size: String? = nil
+public var face: String? = nil
+public var char: String? = nil
+public var cite: String? = nil
+public var span: String? = nil
+public var data: String? = nil
+public var axis: String? = nil
+public var Name: String? = nil
+public var name: String? = nil
+public var code: String? = nil
+public var link: String? = nil
+public var lang: String? = nil
+public var cols: String? = nil
+public var rows: String? = nil
+public var ismap: String? = nil
+public var shape: String? = nil
+public var style: String? = nil
+public var alink: String? = nil
+public var width: String? = nil
+public var rules: String? = nil
+public var align: String? = nil
+public var frame: String? = nil
+public var vlink: String? = nil
+public var deferr: String? = nil
+public var color: String? = nil
+public var media: String? = nil
+public var title: String? = nil
+public var scope: String? = nil
+public var classs: String? = nil
+public var value: String? = nil
+public var clear: String? = nil
+public var start: String? = nil
+public var label: String? = nil
+public var action: String? = nil
+public var height: String? = nil
+public var method: String? = nil
+public var acceptt: String? = nil
+public var object: String? = nil
+public var scheme: String? = nil
+public var coords: String? = nil
+public var usemap: String? = nil
+public var onblur: String? = nil
+public var nohref: String? = nil
+public var nowrap: String? = nil
+public var hspace: String? = nil
+public var border: String? = nil
+public var valign: String? = nil
+public var vspace: String? = nil
+public var onload: String? = nil
+public var target: String? = nil
+public var prompt: String? = nil
+public var onfocus: String? = nil
+public var enctype: String? = nil
+public var onclick: String? = nil
+public var onkeyup: String? = nil
+public var profile: String? = nil
+public var version: String? = nil
+public var onreset: String? = nil
+public var charset: String? = nil
+public var standby: String? = nil
+public var colspan: String? = nil
+public var charoff: String? = nil
+public var classid: String? = nil
+public var compact: String? = nil
+public var declare: String? = nil
+public var rowspan: String? = nil
+public var checked: String? = nil
+public var archive: String? = nil
+public var bgcolor: String? = nil
+public var content: String? = nil
+public var noshade: String? = nil
+public var summary: String? = nil
+public var headers: String? = nil
+public var onselect: String? = nil
+public var readonly: String? = nil
+public var tabindex: String? = nil
+public var onchange: String? = nil
+public var noresize: String? = nil
+public var disabled: String? = nil
+public var longdesc: String? = nil
+public var codebase: String? = nil
+public var language: String? = nil
+public var datetime: String? = nil
+public var selected: String? = nil
+public var hreflang: String? = nil
+public var onsubmit: String? = nil
+public var multiple: String? = nil
+public var onunload: String? = nil
+public var codetype: String? = nil
+public var scrolling: String? = nil
+public var onkeydown: String? = nil
+public var maxlength: String? = nil
+public var valuetype: String? = nil
+public var accesskey: String? = nil
+public var onmouseup: String? = nil
+public var autofocus: String? = nil
+public var onkeypress: String? = nil
+public var ondblclick: String? = nil
+public var onmouseout: String? = nil
+public var httpEquiv: String? = nil
+public var background: String? = nil
+public var onmousemove: String? = nil
+public var onmouseover: String? = nil
+public var cellpadding: String? = nil
+public var onmousedown: String? = nil
+public var frameborder: String? = nil
+public var marginwidth: String? = nil
+public var cellspacing: String? = nil
+public var placeholder: String? = nil
+public var marginheight: String? = nil
+public var acceptCharset: String? = nil
+
+public var inner: String? = nil
+
+public func a(_ c: Closure) { element("a", c) }
+public func b(_ c: Closure) { element("b", c) }
+public func i(_ c: Closure) { element("i", c) }
+public func p(_ c: Closure) { element("p", c) }
+public func q(_ c: Closure) { element("q", c) }
+public func s(_ c: Closure) { element("s", c) }
+public func u(_ c: Closure) { element("u", c) }
+
+public func br(_ c: Closure) { element("br", c) }
+public func dd(_ c: Closure) { element("dd", c) }
+public func dl(_ c: Closure) { element("dl", c) }
+public func dt(_ c: Closure) { element("dt", c) }
+public func em(_ c: Closure) { element("em", c) }
+public func hr(_ c: Closure) { element("hr", c) }
+public func li(_ c: Closure) { element("li", c) }
+public func ol(_ c: Closure) { element("ol", c) }
+public func rp(_ c: Closure) { element("rp", c) }
+public func rt(_ c: Closure) { element("rt", c) }
+public func td(_ c: Closure) { element("td", c) }
+public func th(_ c: Closure) { element("th", c) }
+public func tr(_ c: Closure) { element("tr", c) }
+public func tt(_ c: Closure) { element("tt", c) }
+public func ul(_ c: Closure) { element("ul", c) }
+
+public func ul<T: Sequence>(_ collection: T, _ c: @escaping (T.Iterator.Element) -> Void) {
+    element("ul", {
+        for item in collection {
+            c(item)
+        }
+    })
+}
+
+public func h1(_ c: Closure) { element("h1", c) }
+public func h2(_ c: Closure) { element("h2", c) }
+public func h3(_ c: Closure) { element("h3", c) }
+public func h4(_ c: Closure) { element("h4", c) }
+public func h5(_ c: Closure) { element("h5", c) }
+public func h6(_ c: Closure) { element("h6", c) }
+
+public func bdi(_ c: Closure) { element("bdi", c) }
+public func bdo(_ c: Closure) { element("bdo", c) }
+public func big(_ c: Closure) { element("big", c) }
+public func col(_ c: Closure) { element("col", c) }
+public func del(_ c: Closure) { element("del", c) }
+public func dfn(_ c: Closure) { element("dfn", c) }
+public func dir(_ c: Closure) { element("dir", c) }
+public func div(_ c: Closure) { element("div", c) }
+public func img(_ c: Closure) { element("img", c) }
+public func ins(_ c: Closure) { element("ins", c) }
+public func kbd(_ c: Closure) { element("kbd", c) }
+public func map(_ c: Closure) { element("map", c) }
+public func nav(_ c: Closure) { element("nav", c) }
+public func pre(_ c: Closure) { element("pre", c) }
+public func rtc(_ c: Closure) { element("rtc", c) }
+public func sub(_ c: Closure) { element("sub", c) }
+public func sup(_ c: Closure) { element("sup", c) }
+
+public func varr(_ c: Closure) { element("var", c) }
+public func wbr(_ c: Closure) { element("wbr", c) }
+public func xmp(_ c: Closure) { element("xmp", c) }
+
+public func abbr(_ c: Closure) { element("abbr", c) }
+public func area(_ c: Closure) { element("area", c) }
+public func base(_ c: Closure) { element("base", c) }
+public func body(_ c: Closure) { element("body", c) }
+public func cite(_ c: Closure) { element("cite", c) }
+public func code(_ c: Closure) { element("code", c) }
+public func data(_ c: Closure) { element("data", c) }
+public func font(_ c: Closure) { element("font", c) }
+public func form(_ c: Closure) { element("form", c) }
+public func head(_ c: Closure) { element("head", c) }
+public func html(_ c: Closure) { element("html", c) }
+public func link(_ c: Closure) { element("link", c) }
+public func main(_ c: Closure) { element("main", c) }
+public func mark(_ c: Closure) { element("mark", c) }
+public func menu(_ c: Closure) { element("menu", c) }
+public func meta(_ c: Closure) { element("meta", c) }
+public func nobr(_ c: Closure) { element("nobr", c) }
+public func ruby(_ c: Closure) { element("ruby", c) }
+public func samp(_ c: Closure) { element("samp", c) }
+public func span(_ c: Closure) { element("span", c) }
+public func time(_ c: Closure) { element("time", c) }
+
+public func aside(_ c: Closure) { element("aside", c) }
+public func audio(_ c: Closure) { element("audio", c) }
+public func blink(_ c: Closure) { element("blink", c) }
+public func embed(_ c: Closure) { element("embed", c) }
+public func frame(_ c: Closure) { element("frame", c) }
+public func image(_ c: Closure) { element("image", c) }
+public func input(_ c: Closure) { element("input", c) }
+public func label(_ c: Closure) { element("label", c) }
+public func meter(_ c: Closure) { element("meter", c) }
+public func param(_ c: Closure) { element("param", c) }
+public func small(_ c: Closure) { element("small", c) }
+public func style(_ c: Closure) { element("style", c) }
+public func table(_ c: Closure) { element("table", c) }
+
+public func table<T: Sequence>(_ collection: T, c: @escaping (T.Iterator.Element) -> Void) {
+    element("table", {
+        for item in collection {
+            c(item)
+        }
+    })
+}
+
+public func tbody(_ c: Closure) { element("tbody", c) }
+
+public func tbody<T: Sequence>(_ collection: T, c: @escaping (T.Iterator.Element) -> Void) {
+    element("tbody", {
+        for item in collection {
+            c(item)
+        }
+    })
+}
+
+public func tfoot(_ c: Closure) { element("tfoot", c) }
+public func thead(_ c: Closure) { element("thead", c) }
+public func title(_ c: Closure) { element("title", c) }
+public func track(_ c: Closure) { element("track", c) }
+public func video(_ c: Closure) { element("video", c) }
+
+public func applet(_ c: Closure) { element("applet", c) }
+public func button(_ c: Closure) { element("button", c) }
+public func canvas(_ c: Closure) { element("canvas", c) }
+public func center(_ c: Closure) { element("center", c) }
+public func dialog(_ c: Closure) { element("dialog", c) }
+public func figure(_ c: Closure) { element("figure", c) }
+public func footer(_ c: Closure) { element("footer", c) }
+public func header(_ c: Closure) { element("header", c) }
+public func hgroup(_ c: Closure) { element("hgroup", c) }
+public func iframe(_ c: Closure) { element("iframe", c) }
+public func keygen(_ c: Closure) { element("keygen", c) }
+public func legend(_ c: Closure) { element("legend", c) }
+public func object(_ c: Closure) { element("object", c) }
+public func option(_ c: Closure) { element("option", c) }
+public func output(_ c: Closure) { element("output", c) }
+public func script(_ c: Closure) { element("script", c) }
+public func select(_ c: Closure) { element("select", c) }
+public func shadow(_ c: Closure) { element("shadow", c) }
+public func source(_ c: Closure) { element("source", c) }
+public func spacer(_ c: Closure) { element("spacer", c) }
+public func strike(_ c: Closure) { element("strike", c) }
+public func strong(_ c: Closure) { element("strong", c) }
+
+public func acronym(_ c: Closure) { element("acronym", c) }
+public func address(_ c: Closure) { element("address", c) }
+public func article(_ c: Closure) { element("article", c) }
+public func bgsound(_ c: Closure) { element("bgsound", c) }
+public func caption(_ c: Closure) { element("caption", c) }
+public func command(_ c: Closure) { element("command", c) }
+public func content(_ c: Closure) { element("content", c) }
+public func details(_ c: Closure) { element("details", c) }
+public func elementt(_ c: Closure) { element("element", c) }
+public func isindex(_ c: Closure) { element("isindex", c) }
+public func listing(_ c: Closure) { element("listing", c) }
+public func marquee(_ c: Closure) { element("marquee", c) }
+public func noembed(_ c: Closure) { element("noembed", c) }
+public func picture(_ c: Closure) { element("picture", c) }
+public func section(_ c: Closure) { element("section", c) }
+public func summary(_ c: Closure) { element("summary", c) }
+
+public func basefont(_ c: Closure) { element("basefont", c) }
+public func colgroup(_ c: Closure) { element("colgroup", c) }
+public func datalist(_ c: Closure) { element("datalist", c) }
+public func fieldset(_ c: Closure) { element("fieldset", c) }
+public func frameset(_ c: Closure) { element("frameset", c) }
+public func menuitem(_ c: Closure) { element("menuitem", c) }
+public func multicol(_ c: Closure) { element("multicol", c) }
+public func noframes(_ c: Closure) { element("noframes", c) }
+public func noscript(_ c: Closure) { element("noscript", c) }
+public func optgroup(_ c: Closure) { element("optgroup", c) }
+public func progress(_ c: Closure) { element("progress", c) }
+public func template(_ c: Closure) { element("template", c) }
+public func textarea(_ c: Closure) { element("textarea", c) }
+
+public func plaintext(_ c: Closure) { element("plaintext", c) }
+public func javascript(_ c: Closure) { element("script", ["type": "text/javascript"], c) }
+public func blockquote(_ c: Closure) { element("blockquote", c) }
+public func figcaption(_ c: Closure) { element("figcaption", c) }
+
+public func stylesheet(_ c: Closure) { element("link", ["rel": "stylesheet", "type": "text/css"], c) }
+
+public func element(_ node: String, _ c: Closure) { evaluate(node, [:], c) }
+public func element(_ node: String, _ attrs: [String: String?] = [:], _ c: Closure) { evaluate(node, attrs, c) }
+
+var ScopesBuffer = [UInt64: String]()
+
+private func evaluate(_ node: String, _ attrs: [String: String?] = [:], _ c: Closure) {
+    
+    // Push the attributes.
+    
+    let stackid = idd
+    let stackdir = dir
+    let stackrel = rel
+    let stackrev = rev
+    let stackalt = alt
+    let stackfor = forr
+    let stacksrc = src
+    let stacktype = type
+    let stackhref = href
+    let stacktext = text
+    let stackabbr = abbr
+    let stacksize = size
+    let stackface = face
+    let stackchar = char
+    let stackcite = cite
+    let stackspan = span
+    let stackdata = data
+    let stackaxis = axis
+    let stackName = Name
+    let stackname = name
+    let stackcode = code
+    let stacklink = link
+    let stacklang = lang
+    let stackcols = cols
+    let stackrows = rows
+    let stackismap = ismap
+    let stackshape = shape
+    let stackstyle = style
+    let stackalink = alink
+    let stackwidth = width
+    let stackrules = rules
+    let stackalign = align
+    let stackframe = frame
+    let stackvlink = vlink
+    let stackdefer = deferr
+    let stackcolor = color
+    let stackmedia = media
+    let stacktitle = title
+    let stackscope = scope
+    let stackclass = classs
+    let stackvalue = value
+    let stackclear = clear
+    let stackstart = start
+    let stacklabel = label
+    let stackaction = action
+    let stackheight = height
+    let stackmethod = method
+    let stackaccept = acceptt
+    let stackobject = object
+    let stackscheme = scheme
+    let stackcoords = coords
+    let stackusemap = usemap
+    let stackonblur = onblur
+    let stacknohref = nohref
+    let stacknowrap = nowrap
+    let stackhspace = hspace
+    let stackborder = border
+    let stackvalign = valign
+    let stackvspace = vspace
+    let stackonload = onload
+    let stacktarget = target
+    let stackprompt = prompt
+    let stackonfocus = onfocus
+    let stackenctype = enctype
+    let stackonclick = onclick
+    let stackonkeyup = onkeyup
+    let stackprofile = profile
+    let stackversion = version
+    let stackonreset = onreset
+    let stackcharset = charset
+    let stackstandby = standby
+    let stackcolspan = colspan
+    let stackcharoff = charoff
+    let stackclassid = classid
+    let stackcompact = compact
+    let stackdeclare = declare
+    let stackrowspan = rowspan
+    let stackchecked = checked
+    let stackarchive = archive
+    let stackbgcolor = bgcolor
+    let stackcontent = content
+    let stacknoshade = noshade
+    let stacksummary = summary
+    let stackheaders = headers
+    let stackonselect = onselect
+    let stackreadonly = readonly
+    let stacktabindex = tabindex
+    let stackonchange = onchange
+    let stacknoresize = noresize
+    let stackdisabled = disabled
+    let stacklongdesc = longdesc
+    let stackcodebase = codebase
+    let stacklanguage = language
+    let stackdatetime = datetime
+    let stackselected = selected
+    let stackhreflang = hreflang
+    let stackonsubmit = onsubmit
+    let stackmultiple = multiple
+    let stackonunload = onunload
+    let stackcodetype = codetype
+    let stackscrolling = scrolling
+    let stackonkeydown = onkeydown
+    let stackmaxlength = maxlength
+    let stackvaluetype = valuetype
+    let stackaccesskey = accesskey
+    let stackonmouseup = onmouseup
+    let stackonkeypress = onkeypress
+    let stackondblclick = ondblclick
+    let stackonmouseout = onmouseout
+    let stackhttpEquiv = httpEquiv
+    let stackbackground = background
+    let stackonmousemove = onmousemove
+    let stackonmouseover = onmouseover
+    let stackcellpadding = cellpadding
+    let stackonmousedown = onmousedown
+    let stackframeborder = frameborder
+    let stackmarginwidth = marginwidth
+    let stackcellspacing = cellspacing
+    let stackplaceholder = placeholder
+    let stackmarginheight = marginheight
+    let stackacceptCharset = acceptCharset
+    let stackinner = inner
+    
+    // Reset the values before a nested scope evalutation.
+    
+    idd = nil
+    dir = nil
+    rel = nil
+    rev = nil
+    alt = nil
+    forr = nil
+    src = nil
+    type = nil
+    href = nil
+    text = nil
+    abbr = nil
+    size = nil
+    face = nil
+    char = nil
+    cite = nil
+    span = nil
+    data = nil
+    axis = nil
+    Name = nil
+    name = nil
+    code = nil
+    link = nil
+    lang = nil
+    cols = nil
+    rows = nil
+    ismap = nil
+    shape = nil
+    style = nil
+    alink = nil
+    width = nil
+    rules = nil
+    align = nil
+    frame = nil
+    vlink = nil
+    deferr = nil
+    color = nil
+    media = nil
+    title = nil
+    scope = nil
+    classs = nil
+    value = nil
+    clear = nil
+    start = nil
+    label = nil
+    action = nil
+    height = nil
+    method = nil
+    acceptt = nil
+    object = nil
+    scheme = nil
+    coords = nil
+    usemap = nil
+    onblur = nil
+    nohref = nil
+    nowrap = nil
+    hspace = nil
+    border = nil
+    valign = nil
+    vspace = nil
+    onload = nil
+    target = nil
+    prompt = nil
+    onfocus = nil
+    enctype = nil
+    onclick = nil
+    onkeyup = nil
+    profile = nil
+    version = nil
+    onreset = nil
+    charset = nil
+    standby = nil
+    colspan = nil
+    charoff = nil
+    classid = nil
+    compact = nil
+    declare = nil
+    rowspan = nil
+    checked = nil
+    archive = nil
+    bgcolor = nil
+    content = nil
+    noshade = nil
+    summary = nil
+    headers = nil
+    onselect = nil
+    readonly = nil
+    tabindex = nil
+    onchange = nil
+    noresize = nil
+    disabled = nil
+    longdesc = nil
+    codebase = nil
+    language = nil
+    datetime = nil
+    selected = nil
+    hreflang = nil
+    onsubmit = nil
+    multiple = nil
+    onunload = nil
+    codetype = nil
+    scrolling = nil
+    onkeydown = nil
+    maxlength = nil
+    valuetype = nil
+    accesskey = nil
+    onmouseup = nil
+    onkeypress = nil
+    ondblclick = nil
+    onmouseout = nil
+    httpEquiv = nil
+    background = nil
+    onmousemove = nil
+    onmouseover = nil
+    cellpadding = nil
+    onmousedown = nil
+    frameborder = nil
+    placeholder = nil
+    marginwidth = nil
+    cellspacing = nil
+    marginheight = nil
+    acceptCharset = nil
+    inner = nil
+    
+    ScopesBuffer[Process.tid] = (ScopesBuffer[Process.tid] ?? "") + "<" + node
+    
+    // Save the current output before the nested scope evalutation.
+    
+    var output = ScopesBuffer[Process.tid] ?? ""
+    
+    // Clear the output buffer for the evalutation.
+    
+    ScopesBuffer[Process.tid] = ""
+    
+    // Evaluate the nested scope.
+    
+    c()
+    
+    // Render attributes set by the evalutation.
+    
+    var mergedAttributes = [String: String?]()
+    
+    if let idd = idd { mergedAttributes["id"] = idd }
+    if let dir = dir { mergedAttributes["dir"] = dir }
+    if let rel = rel { mergedAttributes["rel"] = rel }
+    if let rev = rev { mergedAttributes["rev"] = rev }
+    if let alt = alt { mergedAttributes["alt"] = alt }
+    if let forr = forr { mergedAttributes["for"] = forr }
+    if let src = src { mergedAttributes["src"] = src }
+    if let type = type { mergedAttributes["type"] = type }
+    if let href = href { mergedAttributes["href"] = href }
+    if let text = text { mergedAttributes["text"] = text }
+    if let abbr = abbr { mergedAttributes["abbr"] = abbr }
+    if let size = size { mergedAttributes["size"] = size }
+    if let face = face { mergedAttributes["face"] = face }
+    if let char = char { mergedAttributes["char"] = char }
+    if let cite = cite { mergedAttributes["cite"] = cite }
+    if let span = span { mergedAttributes["span"] = span }
+    if let data = data { mergedAttributes["data"] = data }
+    if let axis = axis { mergedAttributes["axis"] = axis }
+    if let Name = Name { mergedAttributes["Name"] = Name }
+    if let name = name { mergedAttributes["name"] = name }
+    if let code = code { mergedAttributes["code"] = code }
+    if let link = link { mergedAttributes["link"] = link }
+    if let lang = lang { mergedAttributes["lang"] = lang }
+    if let cols = cols { mergedAttributes["cols"] = cols }
+    if let rows = rows { mergedAttributes["rows"] = rows }
+    if let ismap = ismap { mergedAttributes["ismap"] = ismap }
+    if let shape = shape { mergedAttributes["shape"] = shape }
+    if let style = style { mergedAttributes["style"] = style }
+    if let alink = alink { mergedAttributes["alink"] = alink }
+    if let width = width { mergedAttributes["width"] = width }
+    if let rules = rules { mergedAttributes["rules"] = rules }
+    if let align = align { mergedAttributes["align"] = align }
+    if let frame = frame { mergedAttributes["frame"] = frame }
+    if let vlink = vlink { mergedAttributes["vlink"] = vlink }
+    if let deferr = deferr { mergedAttributes["defer"] = deferr }
+    if let color = color { mergedAttributes["color"] = color }
+    if let media = media { mergedAttributes["media"] = media }
+    if let title = title { mergedAttributes["title"] = title }
+    if let scope = scope { mergedAttributes["scope"] = scope }
+    if let classs = classs { mergedAttributes["class"] = classs }
+    if let value = value { mergedAttributes["value"] = value }
+    if let clear = clear { mergedAttributes["clear"] = clear }
+    if let start = start { mergedAttributes["start"] = start }
+    if let label = label { mergedAttributes["label"] = label }
+    if let action = action { mergedAttributes["action"] = action }
+    if let height = height { mergedAttributes["height"] = height }
+    if let method = method { mergedAttributes["method"] = method }
+    if let acceptt = acceptt { mergedAttributes["accept"] = acceptt }
+    if let object = object { mergedAttributes["object"] = object }
+    if let scheme = scheme { mergedAttributes["scheme"] = scheme }
+    if let coords = coords { mergedAttributes["coords"] = coords }
+    if let usemap = usemap { mergedAttributes["usemap"] = usemap }
+    if let onblur = onblur { mergedAttributes["onblur"] = onblur }
+    if let nohref = nohref { mergedAttributes["nohref"] = nohref }
+    if let nowrap = nowrap { mergedAttributes["nowrap"] = nowrap }
+    if let hspace = hspace { mergedAttributes["hspace"] = hspace }
+    if let border = border { mergedAttributes["border"] = border }
+    if let valign = valign { mergedAttributes["valign"] = valign }
+    if let vspace = vspace { mergedAttributes["vspace"] = vspace }
+    if let onload = onload { mergedAttributes["onload"] = onload }
+    if let target = target { mergedAttributes["target"] = target }
+    if let prompt = prompt { mergedAttributes["prompt"] = prompt }
+    if let onfocus = onfocus { mergedAttributes["onfocus"] = onfocus }
+    if let enctype = enctype { mergedAttributes["enctype"] = enctype }
+    if let onclick = onclick { mergedAttributes["onclick"] = onclick }
+    if let onkeyup = onkeyup { mergedAttributes["onkeyup"] = onkeyup }
+    if let profile = profile { mergedAttributes["profile"] = profile }
+    if let version = version { mergedAttributes["version"] = version }
+    if let onreset = onreset { mergedAttributes["onreset"] = onreset }
+    if let charset = charset { mergedAttributes["charset"] = charset }
+    if let standby = standby { mergedAttributes["standby"] = standby }
+    if let colspan = colspan { mergedAttributes["colspan"] = colspan }
+    if let charoff = charoff { mergedAttributes["charoff"] = charoff }
+    if let classid = classid { mergedAttributes["classid"] = classid }
+    if let compact = compact { mergedAttributes["compact"] = compact }
+    if let declare = declare { mergedAttributes["declare"] = declare }
+    if let rowspan = rowspan { mergedAttributes["rowspan"] = rowspan }
+    if let checked = checked { mergedAttributes["checked"] = checked }
+    if let archive = archive { mergedAttributes["archive"] = archive }
+    if let bgcolor = bgcolor { mergedAttributes["bgcolor"] = bgcolor }
+    if let content = content { mergedAttributes["content"] = content }
+    if let noshade = noshade { mergedAttributes["noshade"] = noshade }
+    if let summary = summary { mergedAttributes["summary"] = summary }
+    if let headers = headers { mergedAttributes["headers"] = headers }
+    if let onselect = onselect { mergedAttributes["onselect"] = onselect }
+    if let readonly = readonly { mergedAttributes["readonly"] = readonly }
+    if let tabindex = tabindex { mergedAttributes["tabindex"] = tabindex }
+    if let onchange = onchange { mergedAttributes["onchange"] = onchange }
+    if let noresize = noresize { mergedAttributes["noresize"] = noresize }
+    if let disabled = disabled { mergedAttributes["disabled"] = disabled }
+    if let longdesc = longdesc { mergedAttributes["longdesc"] = longdesc }
+    if let codebase = codebase { mergedAttributes["codebase"] = codebase }
+    if let language = language { mergedAttributes["language"] = language }
+    if let datetime = datetime { mergedAttributes["datetime"] = datetime }
+    if let selected = selected { mergedAttributes["selected"] = selected }
+    if let hreflang = hreflang { mergedAttributes["hreflang"] = hreflang }
+    if let onsubmit = onsubmit { mergedAttributes["onsubmit"] = onsubmit }
+    if let multiple = multiple { mergedAttributes["multiple"] = multiple }
+    if let onunload = onunload { mergedAttributes["onunload"] = onunload }
+    if let codetype = codetype { mergedAttributes["codetype"] = codetype }
+    if let scrolling = scrolling { mergedAttributes["scrolling"] = scrolling }
+    if let onkeydown = onkeydown { mergedAttributes["onkeydown"] = onkeydown }
+    if let maxlength = maxlength { mergedAttributes["maxlength"] = maxlength }
+    if let valuetype = valuetype { mergedAttributes["valuetype"] = valuetype }
+    if let accesskey = accesskey { mergedAttributes["accesskey"] = accesskey }
+    if let onmouseup = onmouseup { mergedAttributes["onmouseup"] = onmouseup }
+    if let onkeypress = onkeypress { mergedAttributes["onkeypress"] = onkeypress }
+    if let ondblclick = ondblclick { mergedAttributes["ondblclick"] = ondblclick }
+    if let onmouseout = onmouseout { mergedAttributes["onmouseout"] = onmouseout }
+    if let httpEquiv = httpEquiv { mergedAttributes["http-equiv"] = httpEquiv }
+    if let background = background { mergedAttributes["background"] = background }
+    if let onmousemove = onmousemove { mergedAttributes["onmousemove"] = onmousemove }
+    if let onmouseover = onmouseover { mergedAttributes["onmouseover"] = onmouseover }
+    if let cellpadding = cellpadding { mergedAttributes["cellpadding"] = cellpadding }
+    if let onmousedown = onmousedown { mergedAttributes["onmousedown"] = onmousedown }
+    if let frameborder = frameborder { mergedAttributes["frameborder"] = frameborder }
+    if let marginwidth = marginwidth { mergedAttributes["marginwidth"] = marginwidth }
+    if let cellspacing = cellspacing { mergedAttributes["cellspacing"] = cellspacing }
+    if let placeholder = placeholder { mergedAttributes["placeholder"] = placeholder }
+    if let marginheight = marginheight { mergedAttributes["marginheight"] = marginheight }
+    if let acceptCharset = acceptCharset { mergedAttributes["accept-charset"] = acceptCharset }
+    
+    for item in attrs.enumerated() {
+        mergedAttributes.updateValue(item.element.1, forKey: item.element.0)
+    }
+    
+    output = output + mergedAttributes.reduce("") {
+        if let value = $0.1.1 {
+            return $0.0 + " \($0.1.0)=\"\(value)\""
+        } else {
+            return $0.0
+        }
+    }
+    
+    if let inner = inner {
+        ScopesBuffer[Process.tid] = output + ">" + (inner) + "</" + node + ">"
+    } else {
+        let current = ScopesBuffer[Process.tid]  ?? ""
+        ScopesBuffer[Process.tid] = output + ">" + current + "</" + node + ">"
+    }
+    
+    // Pop the attributes.
+    
+    idd = stackid
+    dir = stackdir
+    rel = stackrel
+    rev = stackrev
+    alt = stackalt
+    forr = stackfor
+    src = stacksrc
+    type = stacktype
+    href = stackhref
+    text = stacktext
+    abbr = stackabbr
+    size = stacksize
+    face = stackface
+    char = stackchar
+    cite = stackcite
+    span = stackspan
+    data = stackdata
+    axis = stackaxis
+    Name = stackName
+    name = stackname
+    code = stackcode
+    link = stacklink
+    lang = stacklang
+    cols = stackcols
+    rows = stackrows
+    ismap = stackismap
+    shape = stackshape
+    style = stackstyle
+    alink = stackalink
+    width = stackwidth
+    rules = stackrules
+    align = stackalign
+    frame = stackframe
+    vlink = stackvlink
+    deferr = stackdefer
+    color = stackcolor
+    media = stackmedia
+    title = stacktitle
+    scope = stackscope
+    classs = stackclass
+    value = stackvalue
+    clear = stackclear
+    start = stackstart
+    label = stacklabel
+    action = stackaction
+    height = stackheight
+    method = stackmethod
+    acceptt = stackaccept
+    object = stackobject
+    scheme = stackscheme
+    coords = stackcoords
+    usemap = stackusemap
+    onblur = stackonblur
+    nohref = stacknohref
+    nowrap = stacknowrap
+    hspace = stackhspace
+    border = stackborder
+    valign = stackvalign
+    vspace = stackvspace
+    onload = stackonload
+    target = stacktarget
+    prompt = stackprompt
+    onfocus = stackonfocus
+    enctype = stackenctype
+    onclick = stackonclick
+    onkeyup = stackonkeyup
+    profile = stackprofile
+    version = stackversion
+    onreset = stackonreset
+    charset = stackcharset
+    standby = stackstandby
+    colspan = stackcolspan
+    charoff = stackcharoff
+    classid = stackclassid
+    compact = stackcompact
+    declare = stackdeclare
+    rowspan = stackrowspan
+    checked = stackchecked
+    archive = stackarchive
+    bgcolor = stackbgcolor
+    content = stackcontent
+    noshade = stacknoshade
+    summary = stacksummary
+    headers = stackheaders
+    onselect = stackonselect
+    readonly = stackreadonly
+    tabindex = stacktabindex
+    onchange = stackonchange
+    noresize = stacknoresize
+    disabled = stackdisabled
+    longdesc = stacklongdesc
+    codebase = stackcodebase
+    language = stacklanguage
+    datetime = stackdatetime
+    selected = stackselected
+    hreflang = stackhreflang
+    onsubmit = stackonsubmit
+    multiple = stackmultiple
+    onunload = stackonunload
+    codetype = stackcodetype
+    scrolling = stackscrolling
+    onkeydown = stackonkeydown
+    maxlength = stackmaxlength
+    valuetype = stackvaluetype
+    accesskey = stackaccesskey
+    onmouseup = stackonmouseup
+    onkeypress = stackonkeypress
+    ondblclick = stackondblclick
+    onmouseout = stackonmouseout
+    httpEquiv = stackhttpEquiv
+    background = stackbackground
+    onmousemove = stackonmousemove
+    onmouseover = stackonmouseover
+    cellpadding = stackcellpadding
+    onmousedown = stackonmousedown
+    frameborder = stackframeborder
+    placeholder = stackplaceholder
+    marginwidth = stackmarginwidth
+    cellspacing = stackcellspacing
+    marginheight = stackmarginheight
+    acceptCharset = stackacceptCharset
+    
+    inner = stackinner
+}

+ 49 - 0
Sources/Socket+File.swift

@@ -0,0 +1,49 @@
+//
+//  Socket+File.swift
+//  Swifter
+//
+//  Created by Damian Kolakowski on 13/07/16.
+//
+
+import Foundation
+
+#if os(iOS) || os(tvOS) || os (Linux)
+    struct sf_hdtr { }
+    
+    private func sendfileImpl(_ source: UnsafeMutablePointer<FILE>, _ target: Int32, _: off_t, _: UnsafeMutablePointer<off_t>, _: UnsafeMutablePointer<sf_hdtr>, _: Int32) -> Int32 {
+        var buffer = [UInt8](repeating: 0, count: 1024)
+        while true {
+            let readResult = fread(&buffer, 1, buffer.count, source)
+            guard readResult > 0 else {
+                return Int32(readResult)
+            }
+            var writeCounter = 0
+            while writeCounter < readResult {
+                let writeResult = write(target, &buffer + writeCounter, readResult - writeCounter)
+                guard writeResult > 0 else {
+                    return Int32(writeResult)
+                }
+                writeCounter = writeCounter + writeResult
+            }
+        }
+    }
+#endif
+
+extension Socket {
+    
+    public func writeFile(_ file: String.File) throws -> Void {
+        var offset: off_t = 0
+        var sf: sf_hdtr = sf_hdtr()
+        
+        #if os(iOS) || os(tvOS) || os (Linux)
+        let result = sendfileImpl(file.pointer, self.socketFileDescriptor, 0, &offset, &sf, 0)
+        #else
+        let result = sendfile(fileno(file.pointer), self.socketFileDescriptor, 0, &offset, &sf, 0)
+        #endif
+        
+        if result == -1 {
+            throw SocketError.writeFailed("sendfile: " + Errno.description())
+        }
+    }
+    
+}

+ 115 - 0
Sources/Socket+Server.swift

@@ -0,0 +1,115 @@
+//
+//  Socket+Server.swift
+//  Swifter
+//
+//  Created by Damian Kolakowski on 13/07/16.
+//
+
+import Foundation
+
+extension Socket {
+
+    /// - Parameters:
+    ///   - listenAddress: String representation of the address the socket should accept
+    ///       connections from. It should be in IPv4 format if forceIPv4 == true,
+    ///       otherwise - in IPv6.
+    public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN, _ listenAddress: String? = nil) throws -> Socket {
+
+        #if os(Linux)
+            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0)
+        #else
+            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0)
+        #endif
+
+        if socketFileDescriptor == -1 {
+            throw SocketError.socketCreationFailed(Errno.description())
+        }
+
+        var value: Int32 = 1
+        if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
+            let details = Errno.description()
+            Socket.close(socketFileDescriptor)
+            throw SocketError.socketSettingReUseAddrFailed(details)
+        }
+        Socket.setNoSigPipe(socketFileDescriptor)
+
+        var bindResult: Int32 = -1
+        if forceIPv4 {
+            #if os(Linux)
+            var addr = sockaddr_in(
+                sin_family: sa_family_t(AF_INET),
+                sin_port: port.bigEndian,
+                sin_addr: in_addr(s_addr: in_addr_t(0)),
+                sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
+            #else
+            var addr = sockaddr_in(
+                sin_len: UInt8(MemoryLayout<sockaddr_in>.stride),
+                sin_family: UInt8(AF_INET),
+                sin_port: port.bigEndian,
+                sin_addr: in_addr(s_addr: in_addr_t(0)),
+                sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
+            #endif
+            if let address = listenAddress {
+              if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 {
+                // print("\(address) is converted to \(addr.sin_addr).")
+              } else {
+                // print("\(address) is not converted.")
+              }
+            }
+            bindResult = withUnsafePointer(to: &addr) {
+                bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
+            }
+        } else {
+            #if os(Linux)
+            var addr = sockaddr_in6(
+                sin6_family: sa_family_t(AF_INET6),
+                sin6_port: port.bigEndian,
+                sin6_flowinfo: 0,
+                sin6_addr: in6addr_any,
+                sin6_scope_id: 0)
+            #else
+            var addr = sockaddr_in6(
+                sin6_len: UInt8(MemoryLayout<sockaddr_in6>.stride),
+                sin6_family: UInt8(AF_INET6),
+                sin6_port: port.bigEndian,
+                sin6_flowinfo: 0,
+                sin6_addr: in6addr_any,
+                sin6_scope_id: 0)
+            #endif
+            if let address = listenAddress {
+              if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 {
+                //print("\(address) is converted to \(addr.sin6_addr).")
+              } else {
+                //print("\(address) is not converted.")
+              }
+            }
+            bindResult = withUnsafePointer(to: &addr) {
+                bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in6>.size))
+            }
+        }
+
+        if bindResult == -1 {
+            let details = Errno.description()
+            Socket.close(socketFileDescriptor)
+            throw SocketError.bindFailed(details)
+        }
+
+        if listen(socketFileDescriptor, maxPendingConnection) == -1 {
+            let details = Errno.description()
+            Socket.close(socketFileDescriptor)
+            throw SocketError.listenFailed(details)
+        }
+        return Socket(socketFileDescriptor: socketFileDescriptor)
+    }
+    
+    public func acceptClientSocket() throws -> Socket {
+        var addr = sockaddr()
+        var len: socklen_t = 0
+        let clientSocket = accept(self.socketFileDescriptor, &addr, &len)
+        if clientSocket == -1 {
+            throw SocketError.acceptFailed(Errno.description())
+        }
+        Socket.setNoSigPipe(clientSocket)
+        return Socket(socketFileDescriptor: clientSocket)
+    }
+}

+ 89 - 157
Sources/Socket.swift

@@ -5,160 +5,117 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
-/* Low level routines for POSIX sockets */
 
-public enum SocketError: ErrorType {
-    case SocketCreationFailed(String)
-    case SocketSettingReUseAddrFailed(String)
-    case BindFailed(String)
-    case ListenFailed(String)
-    case WriteFailed(String)
-    case GetPeerNameFailed(String)
-    case ConvertingPeerNameFailed
-    case GetNameInfoFailed(String)
-    case AcceptFailed(String)
-    case RecvFailed(String)
+public enum SocketError: Error {
+    case socketCreationFailed(String)
+    case socketSettingReUseAddrFailed(String)
+    case bindFailed(String)
+    case listenFailed(String)
+    case writeFailed(String)
+    case getPeerNameFailed(String)
+    case convertingPeerNameFailed
+    case getNameInfoFailed(String)
+    case acceptFailed(String)
+    case recvFailed(String)
+    case getSockNameFailed(String)
 }
 
-public class Socket: Hashable, Equatable {
-    
-    public class func tcpSocketForListen(port: in_port_t, forceIPv4: Bool = false, maxPendingConnection: Int32 = SOMAXCONN) throws -> Socket {
-        
-        #if os(Linux)
-            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0)
-        #else
-            let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0)
-        #endif
-        
-        if socketFileDescriptor == -1 {
-            throw SocketError.SocketCreationFailed(Socket.descriptionOfLastError())
-        }
-        
-        var value: Int32 = 1
-        if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
-            let details = Socket.descriptionOfLastError()
-            Socket.release(socketFileDescriptor)
-            throw SocketError.SocketSettingReUseAddrFailed(details)
-        }
-        Socket.setNoSigPipe(socketFileDescriptor)
+open class Socket: Hashable, Equatable {
         
-        #if os(Linux)
-            var bindResult: Int32 = -1
-            if forceIPv4 {
-                var addr = sockaddr_in(sin_family: sa_family_t(AF_INET),
-                    sin_port: Socket.htonsPort(port),
-                    sin_addr: in_addr(s_addr: in_addr_t(0)),
-                    sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
-                
-                bindResult = withUnsafePointer(&addr) { bind(socketFileDescriptor, UnsafePointer<sockaddr>($0), socklen_t(sizeof(sockaddr_in))) }
-            } else {
-                var addr = sockaddr_in6(sin6_family: sa_family_t(AF_INET6),
-                    sin6_port: Socket.htonsPort(port),
-                    sin6_flowinfo: 0,
-                    sin6_addr: in6addr_any,
-                    sin6_scope_id: 0)
-                
-                bindResult = withUnsafePointer(&addr) { bind(socketFileDescriptor, UnsafePointer<sockaddr>($0), socklen_t(sizeof(sockaddr_in6))) }
-            }
-        #else
-            var bindResult: Int32 = -1
-            if forceIPv4 {
-                var addr = sockaddr_in(sin_len: UInt8(strideof(sockaddr_in)),
-                    sin_family: UInt8(AF_INET),
-                    sin_port: Socket.htonsPort(port),
-                    sin_addr: in_addr(s_addr: in_addr_t(0)),
-                    sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
-             
-                bindResult = withUnsafePointer(&addr) { bind(socketFileDescriptor, UnsafePointer<sockaddr>($0), socklen_t(sizeof(sockaddr_in))) }
-            } else {
-                var addr = sockaddr_in6(sin6_len: UInt8(strideof(sockaddr_in6)),
-                    sin6_family: UInt8(AF_INET6),
-                    sin6_port: Socket.htonsPort(port),
-                    sin6_flowinfo: 0,
-                    sin6_addr: in6addr_any,
-                    sin6_scope_id: 0)
-                
-                bindResult = withUnsafePointer(&addr) { bind(socketFileDescriptor, UnsafePointer<sockaddr>($0), socklen_t(sizeof(sockaddr_in6))) }
-            }
-        #endif
+    let socketFileDescriptor: Int32
+    private var shutdown = false
 
-        if bindResult == -1 {
-            let details = Socket.descriptionOfLastError()
-            Socket.release(socketFileDescriptor)
-            throw SocketError.BindFailed(details)
-        }
-        
-        if listen(socketFileDescriptor, maxPendingConnection ) == -1 {
-            let details = Socket.descriptionOfLastError()
-            Socket.release(socketFileDescriptor)
-            throw SocketError.ListenFailed(details)
-        }
-        return Socket(socketFileDescriptor: socketFileDescriptor)
-    }
-    
-    private let socketFileDescriptor: Int32
     
     public init(socketFileDescriptor: Int32) {
         self.socketFileDescriptor = socketFileDescriptor
     }
     
+    deinit {
+        close()
+    }
+    
     public var hashValue: Int { return Int(self.socketFileDescriptor) }
     
-    public func release() {
-        Socket.release(self.socketFileDescriptor)
+    public func close() {
+        if shutdown {
+            return
+        }
+        shutdown = true
+        Socket.close(self.socketFileDescriptor)
     }
     
-    public func shutdwn() {
-        Socket.shutdwn(self.socketFileDescriptor)
+    public func port() throws -> in_port_t {
+        var addr = sockaddr_in()
+        return try withUnsafePointer(to: &addr) { pointer in
+            var len = socklen_t(MemoryLayout<sockaddr_in>.size)
+            if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 {
+                throw SocketError.getSockNameFailed(Errno.description())
+            }
+            #if os(Linux)
+                return ntohs(addr.sin_port)
+            #else
+                return Int(OSHostByteOrder()) != OSLittleEndian ? addr.sin_port.littleEndian : addr.sin_port.bigEndian
+            #endif
+        }
     }
     
-    public func acceptClientSocket() throws -> Socket {
-        var addr = sockaddr()        
-        var len: socklen_t = 0
-        let clientSocket = accept(self.socketFileDescriptor, &addr, &len)
-        if clientSocket == -1 {
-            throw SocketError.AcceptFailed(Socket.descriptionOfLastError())
+    public func isIPv4() throws -> Bool {
+        var addr = sockaddr_in()
+        return try withUnsafePointer(to: &addr) { pointer in
+            var len = socklen_t(MemoryLayout<sockaddr_in>.size)
+            if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 {
+                throw SocketError.getSockNameFailed(Errno.description())
+            }
+            return Int32(addr.sin_family) == AF_INET
         }
-        Socket.setNoSigPipe(clientSocket)
-        return Socket(socketFileDescriptor: clientSocket)
     }
     
-    public func writeUTF8(string: String) throws {
+    public func writeUTF8(_ string: String) throws {
         try writeUInt8(ArraySlice(string.utf8))
     }
     
-    public func writeUInt8(data: [UInt8]) throws {
+    public func writeUInt8(_ data: [UInt8]) throws {
         try writeUInt8(ArraySlice(data))
     }
     
-    public func writeUInt8(data: ArraySlice<UInt8>) throws {
+    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(Socket.descriptionOfLastError())
-                }
-                sent += s
+            try writeBuffer($0.baseAddress!, length: data.count)
+        }
+    }
+
+    public func writeData(_ data: NSData) throws {
+        try writeBuffer(data.bytes, length: data.length)
+    }
+    
+    public func writeData(_ data: Data) throws {
+        try data.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) -> Void in
+            try self.writeBuffer(pointer, length: data.count)
+        }
+    }
+
+    private func writeBuffer(_ pointer: UnsafeRawPointer, 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
         }
     }
     
-    public func read() throws -> UInt8 {
-        var buffer = [UInt8](count: 1, repeatedValue: 0)
+    open func read() throws -> UInt8 {
+        var buffer = [UInt8](repeating: 0, count: 1)
         let next = recv(self.socketFileDescriptor as Int32, &buffer, Int(buffer.count), 0)
         if next <= 0 {
-            throw SocketError.RecvFailed(Socket.descriptionOfLastError())
+            throw SocketError.recvFailed(Errno.description())
         }
         return buffer[0]
     }
@@ -177,62 +134,37 @@ public class Socket: Hashable, Equatable {
     }
     
     public func peername() throws -> String {
-        var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
+        var addr = sockaddr(), len: socklen_t = socklen_t(MemoryLayout<sockaddr>.size)
         if getpeername(self.socketFileDescriptor, &addr, &len) != 0 {
-            throw SocketError.GetPeerNameFailed(Socket.descriptionOfLastError())
+            throw SocketError.getPeerNameFailed(Errno.description())
         }
-        var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
+        var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
         if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
-            throw SocketError.GetNameInfoFailed(Socket.descriptionOfLastError())
-        }
-        guard let name = String.fromCString(hostBuffer) else {
-            throw SocketError.ConvertingPeerNameFailed
+            throw SocketError.getNameInfoFailed(Errno.description())
         }
-        return name
+        return String(cString: hostBuffer)
     }
     
-    private class func descriptionOfLastError() -> String {
-        return String.fromCString(UnsafePointer(strerror(errno))) ?? "Error: \(errno)"
-    }
-    
-    private class func setNoSigPipe(socket: Int32) {
+    public class func setNoSigPipe(_ socket: Int32) {
         #if os(Linux)
             // There is no SO_NOSIGPIPE in Linux (nor some other systems). You can instead use the MSG_NOSIGNAL flag when calling send(),
             // or use signal(SIGPIPE, SIG_IGN) to make your entire application ignore SIGPIPE.
         #else
             // Prevents crashes when blocking calls are pending and the app is paused ( via Home button ).
             var no_sig_pipe: Int32 = 1
-            setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(sizeof(Int32)))
-        #endif
-    }
-    
-    private class func shutdwn(socket: Int32) {
-        #if os(Linux)
-            shutdown(socket, Int32(SHUT_RDWR))
-        #else
-            Darwin.shutdown(socket, SHUT_RDWR)
-        #endif
-    }
-    
-    private class func release(socket: Int32) {
-        #if os(Linux)
-            shutdown(socket, Int32(SHUT_RDWR))
-        #else
-            Darwin.shutdown(socket, SHUT_RDWR)
+            setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout<Int32>.size))
         #endif
-        close(socket)
     }
     
-    private class func htonsPort(port: in_port_t) -> in_port_t {
+    public class func close(_ socket: Int32) {
         #if os(Linux)
-            return htons(port)
+            let _ = Glibc.close(socket)
         #else
-            let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
-            return isLittleEndian ? _OSSwapInt16(port) : port
+            let _ = Darwin.close(socket)
         #endif
     }
 }
 
-public func ==(socket1: Socket, socket2: Socket) -> Bool {
+public func == (socket1: Socket, socket2: Socket) -> Bool {
     return socket1.socketFileDescriptor == socket2.socketFileDescriptor
 }

+ 6 - 10
Sources/String+BASE64.swift

@@ -5,24 +5,20 @@
 //  Copyright © 2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
 
 extension String {
     
     private static let CODES = [UInt8]("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".utf8)
     
-    public static func toBase64(data: [UInt8]) -> String {
+    public static func toBase64(_ data: [UInt8]) -> String? {
         
         // Based on: https://en.wikipedia.org/wiki/Base64#Sample_Implementation_in_Java
         
         var result = [UInt8]()
         var tmp: UInt8
-        for index in 0.stride(to: data.count, by: 3) {
+        for index in stride(from: 0, to: data.count, by: 3) {
             let byte = data[index]
             tmp = (byte & 0xFC) >> 2;
             result.append(CODES[Int(tmp)])
@@ -38,13 +34,13 @@ extension String {
                     result.append(CODES[Int(tmp)]);
                 } else  {
                     result.append(CODES[Int(tmp)]);
-                    result.appendContentsOf([UInt8]("=".utf8));
+                    result.append(contentsOf: [UInt8]("=".utf8));
                 }
             } else {
                 result.append(CODES[Int(tmp)]);
-                result.appendContentsOf([UInt8]("==".utf8));
+                result.append(contentsOf: [UInt8]("==".utf8));
             }
         }
-        return String.fromUInt8(result)
+        return String(bytes: result, encoding: .utf8)
     }
 }

+ 144 - 0
Sources/String+File.swift

@@ -0,0 +1,144 @@
+//
+//  String+File.swift
+//  Swifter
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+
+extension String {
+    
+    public enum FileError: Error {
+        case error(Int32)
+    }
+    
+    public class File {
+        
+        let pointer: UnsafeMutablePointer<FILE>
+        
+        public init(_ pointer: UnsafeMutablePointer<FILE>) {
+            self.pointer = pointer
+        }
+        
+        public func close() -> Void {
+            fclose(pointer)
+        }
+        
+        public func seek(_ offset: Int) -> Bool {
+            return (fseek(pointer, offset, SEEK_SET) == 0)
+        }
+        
+        public func read(_ data: inout [UInt8]) throws -> Int {
+            if data.count <= 0 {
+                return data.count
+            }
+            let count = fread(&data, 1, data.count, self.pointer)
+            if count == data.count {
+                return count
+            }
+            if feof(self.pointer) != 0 {
+                return count
+            }
+            if ferror(self.pointer) != 0 {
+                throw FileError.error(errno)
+            }
+            throw FileError.error(0)
+        }
+        
+        public func write(_ data: [UInt8]) throws -> Void {
+            if data.count <= 0 {
+                return
+            }
+            try data.withUnsafeBufferPointer {
+                if fwrite($0.baseAddress, 1, data.count, self.pointer) != data.count {
+                    throw FileError.error(errno)
+                }
+            }
+        }
+        
+        public static func currentWorkingDirectory() throws -> String {
+            guard let path = getcwd(nil, 0) else {
+                throw FileError.error(errno)
+            }
+            return String(cString: path)
+        }
+    }
+    
+    public static var pathSeparator = "/"
+    
+    public func openNewForWriting() throws -> File {
+        return try openFileForMode(self, "wb")
+    }
+    
+    public func openForReading() throws -> File {
+        return try openFileForMode(self, "rb")
+    }
+    
+    public func openForWritingAndReading() throws -> File {
+        return try openFileForMode(self, "r+b")
+    }
+    
+    public func openFileForMode(_ path: String, _ mode: String) throws -> File {
+        guard let file = path.withCString({ pathPointer in mode.withCString({ fopen(pathPointer, $0) }) }) else {
+            throw FileError.error(errno)
+        }
+        return File(file)
+    }
+    
+    public func exists() throws -> Bool {
+        return try self.withStat {
+            if let _ = $0 {
+                return true
+            }
+            return false
+        }
+    }
+    
+    public func directory() throws -> Bool {
+        return try self.withStat {
+            if let stat = $0 {
+                return stat.st_mode & S_IFMT == S_IFDIR
+            }
+            return false
+        }
+    }
+    
+    public func files() throws -> [String] {
+        guard let dir = self.withCString({ opendir($0) }) else {
+            throw FileError.error(errno)
+        }
+        defer { closedir(dir) }
+        var results = [String]()
+        while let ent = readdir(dir) {
+            var name = ent.pointee.d_name
+            let fileName = withUnsafePointer(to: &name) { (ptr) -> String? in
+                #if os(Linux)
+                    return String(validatingUTF8: [CChar](UnsafeBufferPointer<CChar>(start: UnsafePointer(unsafeBitCast(ptr, to: UnsafePointer<CChar>.self)), count: 256)))
+                #else
+                    var buffer = [CChar](UnsafeBufferPointer(start: unsafeBitCast(ptr, to: UnsafePointer<CChar>.self), count: Int(ent.pointee.d_namlen)))
+                    buffer.append(0)
+                    return String(validatingUTF8: buffer)
+                #endif
+            }
+            if let fileName = fileName {
+                results.append(fileName)
+            }
+        }
+        return results
+    }
+    
+    private func withStat<T>(_ closure: ((stat?) throws -> T)) throws -> T {
+        return try self.withCString({
+            var statBuffer = stat()
+            if stat($0, &statBuffer) == 0 {
+                return try closure(statBuffer)
+            }
+            if errno == ENOENT {
+                return try closure(nil)
+            }
+            throw FileError.error(errno)
+        })
+    }
+}

+ 2 - 76
Sources/String+Misc.swift

@@ -5,27 +5,10 @@
 //  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
-extension String {
 
-    public func split(separator: Character) -> [String] {
-        return self.characters.split { $0 == separator }.map(String.init)
-    }
-    
-    public func split(maxSplit: Int = Int.max, separator: Character) -> [String] {
-        return self.characters.split(maxSplit) { $0 == separator }.map(String.init)
-    }
-    
-    public func replace(old: Character, _ new: Character) -> String {
-        var buffer = [Character]()
-        self.characters.forEach { buffer.append($0 == old ? new : $0) }
-        return String(buffer)
-    }
+extension String {
     
     public func unquote() -> String {
         var scalars = self.unicodeScalars;
@@ -36,51 +19,6 @@ extension String {
         }
         return self
     }
-    
-    public func trim() -> String {
-        var scalars = self.unicodeScalars
-        while let _ = scalars.first?.asWhitespace() { scalars.removeFirst() }
-        while let _ = scalars.last?.asWhitespace() { scalars.removeLast() }
-        return String(scalars)
-    }
-    
-    public static func fromUInt8(array: [UInt8]) -> String {
-        // Apple changes the definition of String(data: .... ) every release so let's stay with 'fromUInt8(...)' wrapper.
-        return array.reduce("", combine: { $0.0 + String(UnicodeScalar($0.1)) })
-    }
-    
-    public func removePercentEncoding() -> String {
-        var scalars = self.unicodeScalars
-        var output = ""
-        var decodeBuffer = [UInt8]()
-        while let scalar = scalars.popFirst() {
-            if scalar == "%" {
-                let first = scalars.popFirst()
-                let secon = scalars.popFirst()
-                if let first = first?.asAlpha(), secon = secon?.asAlpha() {
-                    decodeBuffer.append(first*16+secon)
-                } else {
-                    if !decodeBuffer.isEmpty {
-                        output.appendContentsOf(String.fromUInt8(decodeBuffer))
-                        decodeBuffer.removeAll()
-                    }
-                    if let first = first { output.append(Character(first)) }
-                    if let secon = secon { output.append(Character(secon)) }
-                }
-            } else {
-                if !decodeBuffer.isEmpty {
-                    output.appendContentsOf(String.fromUInt8(decodeBuffer))
-                    decodeBuffer.removeAll()
-                }
-                output.append(Character(scalar))
-            }
-        }
-        if !decodeBuffer.isEmpty {
-            output.appendContentsOf(String.fromUInt8(decodeBuffer))
-            decodeBuffer.removeAll()
-        }
-        return output
-    }
 }
 
 extension UnicodeScalar {
@@ -95,16 +33,4 @@ extension UnicodeScalar {
         return nil
     }
     
-    public func asAlpha() -> UInt8? {
-        if self.value >= 48 && self.value <= 57 {
-            return UInt8(self.value) - 48
-        }
-        if self.value >= 97 && self.value <= 102 {
-            return UInt8(self.value) - 87
-        }
-        if self.value >= 65 && self.value <= 70 {
-            return UInt8(self.value) - 55
-        }
-        return nil
-    }
 }

+ 44 - 46
Sources/String+SHA1.swift

@@ -5,24 +5,16 @@
 //  Copyright 2014-2016 Damian Kołakowski. All rights reserved.
 //
 
-#if os(Linux)
-    import Glibc
-#else
-    import Foundation
-#endif
+import Foundation
 
 
-extension String {
-    
-    public func SHA1() -> String {
-        return SHA1().reduce("") { $0 + String(format: "%02x", $1) }
-    }
+public struct SHA1 {
     
-    public func SHA1() -> [UInt8] {
+    public static func hash(_ input: [UInt8]) -> [UInt8] {
         
         // Alghorithm from: https://en.wikipedia.org/wiki/SHA-1
         
-        var message = [UInt8](self.utf8)
+        var message = input
         
         var h0 = UInt32(littleEndian: 0x67452301)
         var h1 = UInt32(littleEndian: 0xEFCDAB89)
@@ -42,17 +34,17 @@ extension String {
         
         let padBytesCount = ( message.count + 8 ) % 64
         
-        message.appendContentsOf([UInt8](count: 64 - padBytesCount, repeatedValue: 0))
+        message.append(contentsOf: [UInt8](repeating: 0, count: 64 - padBytesCount))
         
         // append ml, in a 64-bit big-endian integer. Thus, the total length is a multiple of 512 bits.
         
         var mlBigEndian = ml.bigEndian
-        let bytePtr = withUnsafePointer(&mlBigEndian) { UnsafeBufferPointer<UInt8>(start: UnsafePointer($0), count: sizeofValue(mlBigEndian)) }
-        
-        message.appendContentsOf(Array(bytePtr))
+        withUnsafePointer(to: &mlBigEndian) {
+            message.append(contentsOf: Array(UnsafeBufferPointer<UInt8>(start: UnsafePointer(OpaquePointer($0)), count: 8)))
+        }
         
         // Process the message in successive 512-bit chunks ( 64 bytes chunks ):
-
+        
         for chunkStart in 0..<message.count/64 {
             var words = [UInt32]()
             let chunk = message[chunkStart*64..<chunkStart*64+64]
@@ -60,14 +52,14 @@ extension String {
             // break chunk into sixteen 32-bit big-endian words w[i], 0 ≤ i ≤ 15
             
             for i in 0...15 {
-                let value = chunk.withUnsafeBufferPointer({ UnsafePointer<UInt32>($0.baseAddress + (i*4)).memory })
+                let value = chunk.withUnsafeBufferPointer({ UnsafePointer<UInt32>(OpaquePointer($0.baseAddress! + (i*4))).pointee})
                 words.append(value.bigEndian)
             }
             
             // Extend the sixteen 32-bit words into eighty 32-bit words:
             
             for i in 16...79 {
-                let value = words[i-3] ^ words[i-8] ^ words[i-14] ^ words[i-16]
+                let value: UInt32 = ((words[i-3]) ^ (words[i-8]) ^ (words[i-14]) ^ (words[i-16]))
                 words.append(rotateLeft(value, 1))
             }
             
@@ -83,19 +75,19 @@ extension String {
                 var f = UInt32(0)
                 var k = UInt32(0)
                 switch i {
-                    case 0...19:
-                        f = (b & c) | ((~b) & d)
-                        k = 0x5A827999
-                    case 20...39:
-                        f = b ^ c ^ d
-                        k = 0x6ED9EBA1
-                    case 40...59:
-                        f = (b & c) | (b & d) | (c & d)
-                        k = 0x8F1BBCDC
-                    case 60...79:
-                        f = b ^ c ^ d
-                        k = 0xCA62C1D6
-                    default: break
+                case 0...19:
+                    f = (b & c) | ((~b) & d)
+                    k = 0x5A827999
+                case 20...39:
+                    f = b ^ c ^ d
+                    k = 0x6ED9EBA1
+                case 40...59:
+                    f = (b & c) | (b & d) | (c & d)
+                    k = 0x8F1BBCDC
+                case 60...79:
+                    f = b ^ c ^ d
+                    k = 0xCA62C1D6
+                default: break
                 }
                 let temp = (rotateLeft(a, 5) &+ f &+ e &+ k &+ words[i]) & 0xFFFFFFFF
                 e = d
@@ -116,24 +108,30 @@ extension String {
         
         // Produce the final hash value (big-endian) as a 160 bit number:
         
-        var result = [UInt8]()
+        var digest = [UInt8]()
         
-        let h0Big = h0.bigEndian
-        let h1Big = h1.bigEndian
-        let h2Big = h2.bigEndian
-        let h3Big = h3.bigEndian
-        let h4Big = h4.bigEndian
+        [h0, h1, h2, h3, h4].forEach { value in
+            var bigEndianVersion = value.bigEndian
+            withUnsafePointer(to: &bigEndianVersion) {
+                digest.append(contentsOf: Array(UnsafeBufferPointer<UInt8>(start: UnsafePointer(OpaquePointer($0)), count: 4)))
+            }
+        }
         
-        result += ([UInt8(h0Big & 0xFF), UInt8((h0Big >> 8) & 0xFF), UInt8((h0Big >> 16) & 0xFF), UInt8((h0Big >> 24) & 0xFF)]);
-        result += ([UInt8(h1Big & 0xFF), UInt8((h1Big >> 8) & 0xFF), UInt8((h1Big >> 16) & 0xFF), UInt8((h1Big >> 24) & 0xFF)]);
-        result += ([UInt8(h2Big & 0xFF), UInt8((h2Big >> 8) & 0xFF), UInt8((h2Big >> 16) & 0xFF), UInt8((h2Big >> 24) & 0xFF)]);
-        result += ([UInt8(h3Big & 0xFF), UInt8((h3Big >> 8) & 0xFF), UInt8((h3Big >> 16) & 0xFF), UInt8((h3Big >> 24) & 0xFF)]);
-        result += ([UInt8(h4Big & 0xff), UInt8((h4Big >> 8) & 0xFF), UInt8((h4Big >> 16) & 0xFF), UInt8((h4Big >> 24) & 0xFF)]);
-
-        return result;
+        return digest
     }
     
-    func rotateLeft(v: UInt32, _ n: UInt32) -> UInt32 {
+    private static func rotateLeft(_ v: UInt32, _ n: UInt32) -> UInt32 {
         return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n))
     }
 }
+
+extension String {
+    
+    public func sha1() -> [UInt8] {
+        return SHA1.hash([UInt8](self.utf8))
+    }
+    
+    public func sha1() -> String {
+        return self.sha1().reduce("") { $0 + String(format: "%02x", $1) }
+    }
+}

+ 275 - 0
Sources/WebSockets.swift

@@ -0,0 +1,275 @@
+//
+//  HttpHandlers+WebSockets.swift
+//  Swifter
+//
+//  Copyright © 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+
+public func websocket(
+      _ text: ((WebSocketSession, String) -> Void)?,
+    _ binary: ((WebSocketSession, [UInt8]) -> Void)?) -> ((HttpRequest) -> HttpResponse) {
+    return { r in
+        guard r.hasTokenForHeader("upgrade", token: "websocket") else {
+            return .badRequest(.text("Invalid value of 'Upgrade' header: \(r.headers["upgrade"])"))
+        }
+        guard r.hasTokenForHeader("connection", token: "upgrade") else {
+            return .badRequest(.text("Invalid value of 'Connection' header: \(r.headers["connection"])"))
+        }
+        guard let secWebSocketKey = r.headers["sec-websocket-key"] else {
+            return .badRequest(.text("Invalid value of 'Sec-Websocket-Key' header: \(r.headers["sec-websocket-key"])"))
+        }
+        let protocolSessionClosure: ((Socket) -> Void) = { socket in
+            let session = WebSocketSession(socket)
+            var fragmentedOpCode = WebSocketSession.OpCode.close
+            var payload = [UInt8]() // Used for fragmented frames.
+            
+            func handleTextPayload(_ frame: WebSocketSession.Frame) throws {
+                if let handleText = text {
+                    if frame.fin {
+                        if payload.count > 0 {
+                            throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.")
+                        }
+                        var textFramePayload = frame.payload.map { Int8(bitPattern: $0) }
+                        textFramePayload.append(0)
+                        if let text = String(validatingUTF8: textFramePayload) {
+                            handleText(session, text)
+                        } else {
+                            throw WebSocketSession.WsError.invalidUTF8("")
+                        }
+                    } else {
+                        payload.append(contentsOf: frame.payload)
+                        fragmentedOpCode = .text
+                    }
+                }
+            }
+            
+            func handleBinaryPayload(_ frame: WebSocketSession.Frame) throws {
+                if let handleBinary = binary {
+                    if frame.fin {
+                        if payload.count > 0 {
+                            throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.")
+                        }
+                        handleBinary(session, frame.payload)
+                    } else {
+                        payload.append(contentsOf: frame.payload)
+                        fragmentedOpCode = .binary
+                    }
+                }
+            }
+            
+            func handleOperationCode(_ frame: WebSocketSession.Frame) throws {
+                switch frame.opcode {
+                case .continue:
+                    // There is no message to continue, failed immediatelly.
+                    if fragmentedOpCode == .close {
+                        socket.close()
+                    }
+                    frame.opcode = fragmentedOpCode
+                    if frame.fin {
+                        payload.append(contentsOf: frame.payload)
+                        frame.payload = payload
+                        // Clean the buffer.
+                        payload = []
+                        // Reset the OpCode.
+                        fragmentedOpCode = WebSocketSession.OpCode.close
+                    }
+                    try handleOperationCode(frame)
+                case .text:
+                    try handleTextPayload(frame)
+                case .binary:
+                    try handleBinaryPayload(frame)
+                case .close:
+                    throw WebSocketSession.Control.close
+                case .ping:
+                    if frame.payload.count > 125 {
+                        throw WebSocketSession.WsError.protocolError("Payload gretter than 125 octets.")
+                    } else {
+                        session.writeFrame(ArraySlice(frame.payload), .pong)
+                    }
+                case .pong:
+                    break
+                }
+            }
+            
+            do {
+                while true {
+                    let frame = try session.readFrame()
+                    try handleOperationCode(frame)
+                }
+            } catch let error {
+                switch error {
+                case WebSocketSession.Control.close:
+                    // Normal close
+                    break
+                case WebSocketSession.WsError.unknownOpCode:
+                    print("Unknown Op Code: \(error)")
+                case WebSocketSession.WsError.unMaskedFrame:
+                    print("Unmasked frame: \(error)")
+                case WebSocketSession.WsError.invalidUTF8:
+                    print("Invalid UTF8 character: \(error)")
+                case WebSocketSession.WsError.protocolError:
+                    print("Protocol error: \(error)")
+                default:
+                    print("Unkown error \(error)")
+                }
+                // If an error occurs, send the close handshake.
+                session.writeCloseFrame()
+            }
+        }
+        guard let secWebSocketAccept = String.toBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").sha1()) else {
+            return HttpResponse.internalServerError
+        }
+        let headers = ["Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept]
+        return HttpResponse.switchProtocols(headers, protocolSessionClosure)
+    }
+}
+
+public class WebSocketSession: Hashable, Equatable  {
+    
+    public enum WsError: Error { case unknownOpCode(String), unMaskedFrame(String), protocolError(String), invalidUTF8(String) }
+    public enum OpCode: UInt8 { case `continue` = 0x00, close = 0x08, ping = 0x09, pong = 0x0A, text = 0x01, binary = 0x02 }
+    public enum Control: Error { case close }
+    
+    public class Frame {
+        public var opcode = OpCode.close
+        public var fin = false
+        public var rsv1: UInt8 = 0
+        public var rsv2: UInt8 = 0
+        public var rsv3: UInt8 = 0
+        public var payload = [UInt8]()
+    }
+
+    let socket: Socket
+    
+    public init(_ socket: Socket) {
+        self.socket = socket
+    }
+    
+    deinit {
+        writeCloseFrame()
+        socket.close()
+    }
+    
+    public func writeText(_ text: String) -> Void {
+        self.writeFrame(ArraySlice(text.utf8), OpCode.text)
+    }
+
+    public func writeBinary(_ binary: [UInt8]) -> Void {
+        self.writeBinary(ArraySlice(binary))
+    }
+    
+    public func writeBinary(_ binary: ArraySlice<UInt8>) -> Void {
+        self.writeFrame(binary, OpCode.binary)
+    }
+    
+    public func writeFrame(_ data: ArraySlice<UInt8>, _ op: OpCode, _ fin: Bool = true) {
+        let finAndOpCode = UInt8(fin ? 0x80 : 0x00) | op.rawValue
+        let maskAndLngth = encodeLengthAndMaskFlag(UInt64(data.count), false)
+        do {
+            try self.socket.writeUInt8([finAndOpCode])
+            try self.socket.writeUInt8(maskAndLngth)
+            try self.socket.writeUInt8(data)
+        } catch {
+            print(error)
+        }
+    }
+    
+    public func writeCloseFrame() {
+        writeFrame(ArraySlice("".utf8), .close)
+    }
+    
+    private func encodeLengthAndMaskFlag(_ len: UInt64, _ masked: Bool) -> [UInt8] {
+        let encodedLngth = UInt8(masked ? 0x80 : 0x00)
+        var encodedBytes = [UInt8]()
+        switch len {
+        case 0...125:
+            encodedBytes.append(encodedLngth | UInt8(len));
+        case 126...UInt64(UINT16_MAX):
+            encodedBytes.append(encodedLngth | 0x7E);
+            encodedBytes.append(UInt8(len >> 8 & 0xFF));
+            encodedBytes.append(UInt8(len >> 0 & 0xFF));
+        default:
+            encodedBytes.append(encodedLngth | 0x7F);
+            encodedBytes.append(UInt8(len >> 56 & 0xFF));
+            encodedBytes.append(UInt8(len >> 48 & 0xFF));
+            encodedBytes.append(UInt8(len >> 40 & 0xFF));
+            encodedBytes.append(UInt8(len >> 32 & 0xFF));
+            encodedBytes.append(UInt8(len >> 24 & 0xFF));
+            encodedBytes.append(UInt8(len >> 16 & 0xFF));
+            encodedBytes.append(UInt8(len >> 08 & 0xFF));
+            encodedBytes.append(UInt8(len >> 00 & 0xFF));
+        }
+        return encodedBytes
+    }
+    
+    public func readFrame() throws -> Frame {
+        let frm = Frame()
+        let fst = try socket.read()
+        frm.fin = fst & 0x80 != 0
+        frm.rsv1 = fst & 0x40
+        frm.rsv2 = fst & 0x20
+        frm.rsv3 = fst & 0x10
+        guard frm.rsv1 == 0 && frm.rsv2 == 0 && frm.rsv3 == 0
+            else {
+            throw WsError.protocolError("Reserved frame bit has not been negocitated.")
+        }
+        let opc = fst & 0x0F
+        guard let opcode = OpCode(rawValue: opc) else {
+            // "If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_."
+            // http://tools.ietf.org/html/rfc6455#section-5.2 ( Page 29 )
+            throw WsError.unknownOpCode("\(opc)")
+        }
+        if frm.fin == false {
+            switch opcode {
+            case .ping, .pong, .close:
+                // Control frames must not be fragmented
+                // https://tools.ietf.org/html/rfc6455#section-5.5 ( Page 35 )
+                throw WsError.protocolError("Control frames must not be fragmented.")
+            default:
+                break
+            }
+        }
+        frm.opcode = opcode
+        let sec = try socket.read()
+        let msk = sec & 0x80 != 0
+        guard msk else {
+            // "...a client MUST mask all frames that it sends to the server."
+            // http://tools.ietf.org/html/rfc6455#section-5.1
+            throw WsError.unMaskedFrame("A client must mask all frames that it sends to the server.")
+        }
+        var len = UInt64(sec & 0x7F)
+        if len == 0x7E {
+            let b0 = UInt64(try socket.read())
+            let b1 = UInt64(try socket.read())
+            len = UInt64(littleEndian: b0 << 8 | b1)
+        } else if len == 0x7F {
+            let b0 = UInt64(try socket.read())
+            let b1 = UInt64(try socket.read())
+            let b2 = UInt64(try socket.read())
+            let b3 = UInt64(try socket.read())
+            let b4 = UInt64(try socket.read())
+            let b5 = UInt64(try socket.read())
+            let b6 = UInt64(try socket.read())
+            let b7 = UInt64(try socket.read())
+            len = UInt64(littleEndian: b0 << 54 | b1 << 48 | b2 << 40 | b3 << 32 | b4 << 24 | b5 << 16 | b6 << 8 | b7)
+        }
+        let mask = [try socket.read(), try socket.read(), try socket.read(), try socket.read()]
+        for i in 0..<len {
+            frm.payload.append(try socket.read() ^ mask[Int(i % 4)])
+        }
+        return frm
+    }
+    
+    public var hashValue: Int {
+        get {
+            return socket.hashValue
+        }
+    }
+}
+
+public func ==(webSocketSession1: WebSocketSession, webSocketSession2: WebSocketSession) -> Bool {
+    return webSocketSession1.socket == webSocketSession2.socket
+}

+ 2 - 2
Swifter.podspec

@@ -1,7 +1,7 @@
 Pod::Spec.new do |s|
 
   s.name                  = "Swifter"
-  s.version               = "1.2.0"
+  s.version               = "1.3.3"
   s.summary               = "Tiny http server engine written in Swift programming language."
   s.homepage              = "https://github.com/glock45/swifter"
   s.license               = { :type => 'Copyright', :file => 'LICENSE' }
@@ -9,7 +9,7 @@ Pod::Spec.new do |s|
   s.ios.deployment_target = "8.0"
   s.osx.deployment_target = "10.9"
   s.tvos.deployment_target = "9.0"
-  s.source                = { :git => "https://github.com/glock45/swifter.git", :tag => "1.2.0" }
+  s.source                = { :git => "https://github.com/glock45/swifter.git", :tag => "1.3.3" }
   s.source_files          = 'Sources/*.{h,m,swift}'
 
 end

+ 0 - 41
Swifter.xcodeproj/project.xcworkspace/xcshareddata/Swifter.xccheckout

@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>IDESourceControlProjectFavoriteDictionaryKey</key>
-	<false/>
-	<key>IDESourceControlProjectIdentifier</key>
-	<string>6F6EE92F-5058-4110-A55D-DFBC76A28E89</string>
-	<key>IDESourceControlProjectName</key>
-	<string>Swifter</string>
-	<key>IDESourceControlProjectOriginsDictionary</key>
-	<dict>
-		<key>75224C288D14A799245F99223249EF48FF5DC280</key>
-		<string>github.com:glock45/swifter.git</string>
-	</dict>
-	<key>IDESourceControlProjectPath</key>
-	<string>Swifter.xcodeproj</string>
-	<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
-	<dict>
-		<key>75224C288D14A799245F99223249EF48FF5DC280</key>
-		<string>../..</string>
-	</dict>
-	<key>IDESourceControlProjectURL</key>
-	<string>github.com:glock45/swifter.git</string>
-	<key>IDESourceControlProjectVersion</key>
-	<integer>111</integer>
-	<key>IDESourceControlProjectWCCIdentifier</key>
-	<string>75224C288D14A799245F99223249EF48FF5DC280</string>
-	<key>IDESourceControlProjectWCConfigurations</key>
-	<array>
-		<dict>
-			<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
-			<string>public.vcs.git</string>
-			<key>IDESourceControlWCCIdentifierKey</key>
-			<string>75224C288D14A799245F99223249EF48FF5DC280</string>
-			<key>IDESourceControlWCCName</key>
-			<string>swifter</string>
-		</dict>
-	</array>
-</dict>
-</plist>

+ 0 - 10
Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/WorkspaceSettings.xcsettings

@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
-	<true/>
-	<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
-	<false/>
-</dict>
-</plist>

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

@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>SchemeUserState</key>
-	<dict>
-		<key>SwifterMac.xcscheme_^#shared#^_</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>1</integer>
-		</dict>
-		<key>SwifterOSXTests.xcscheme</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>4</integer>
-		</dict>
-		<key>SwifterSampleOSX.xcscheme</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>2</integer>
-		</dict>
-		<key>SwifterSampleiOS.xcscheme</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>3</integer>
-		</dict>
-		<key>SwifteriOS.xcscheme_^#shared#^_</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>0</integer>
-		</dict>
-		<key>SwifteriOSTests.xcscheme</key>
-		<dict>
-			<key>orderHint</key>
-			<integer>5</integer>
-		</dict>
-	</dict>
-	<key>SuppressBuildableAutocreation</key>
-	<dict>
-		<key>7AE893E61C05127900A29F63</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7AE893FA1C0512C400A29F63</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7C6EC07F1C614051005A49B3</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7C839B6D19422CFF003A6950</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7C839B7F19422CFF003A6950</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7CA4813A19A2EA8D0030B30D</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7CCD875B1C66099B0068099B</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7CCD87781C660EA30068099B</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7CE581B41A36D9E300FB840F</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-		<key>7CEA69AD1BD271920009F783</key>
-		<dict>
-			<key>primary</key>
-			<true/>
-		</dict>
-	</dict>
-</dict>
-</plist>

+ 0 - 1352
SwifterSampleOSX/SwiftyJSON.swift

@@ -1,1352 +0,0 @@
-//  SwiftyJSON.swift
-//
-//  Copyright (c) 2014 Ruoyu Fu, Pinglin Tang
-//
-//  Permission is hereby granted, free of charge, to any person obtaining a copy
-//  of this software and associated documentation files (the "Software"), to deal
-//  in the Software without restriction, including without limitation the rights
-//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-//  copies of the Software, and to permit persons to whom the Software is
-//  furnished to do so, subject to the following conditions:
-//
-//  The above copyright notice and this permission notice shall be included in
-//  all copies or substantial portions of the Software.
-//
-//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-//  THE SOFTWARE.
-
-import Foundation
-
-// MARK: - Error
-
-///Error domain
-public let ErrorDomain: String! = "SwiftyJSONErrorDomain"
-
-///Error code
-public let ErrorUnsupportedType: Int! = 999
-public let ErrorIndexOutOfBounds: Int! = 900
-public let ErrorWrongType: Int! = 901
-public let ErrorNotExist: Int! = 500
-public let ErrorInvalidJSON: Int! = 490
-
-// MARK: - JSON Type
-
-/**
-JSON's type definitions.
-
-See http://tools.ietf.org/html/rfc7231#section-4.3
-*/
-public enum Type :Int{
-    
-    case Number
-    case String
-    case Bool
-    case Array
-    case Dictionary
-    case Null
-    case Unknown
-}
-
-// MARK: - JSON Base
-
-public struct JSON {
-    
-    /**
-    Creates a JSON using the data.
-    
-    - parameter data:  The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary
-    - parameter opt:   The JSON serialization reading options. `.AllowFragments` by default.
-    - parameter error: error The NSErrorPointer used to return the error. `nil` by default.
-    
-    - returns: The created JSON
-    */
-    public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) {
-        do {
-            let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt)
-            self.init(object)
-        } catch let aError as NSError {
-            if error != nil {
-                error.memory = aError
-            }
-            self.init(NSNull())
-        }
-    }
-    
-    /**
-    Creates a JSON using the object.
-    
-    - parameter object:  The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity.
-    
-    - returns: The created JSON
-    */
-    public init(_ object: AnyObject) {
-        self.object = object
-    }
-    
-    /**
-    Creates a JSON from a [JSON]
-    
-    - parameter jsonArray: A Swift array of JSON objects
-    
-    - returns: The created JSON
-    */
-    public init(_ jsonArray:[JSON]) {
-        self.init(jsonArray.map { $0.object })
-    }
-    
-    /**
-    Creates a JSON from a [String: JSON]
-    
-    - parameter jsonDictionary: A Swift dictionary of JSON objects
-    
-    - returns: The created JSON
-    */
-    public init(_ jsonDictionary:[String: JSON]) {
-        var dictionary = [String: AnyObject]()
-        for (key, json) in jsonDictionary {
-            dictionary[key] = json.object
-        }
-        self.init(dictionary)
-    }
-    
-    /// Private object
-    private var rawArray: [AnyObject] = []
-    private var rawDictionary: [String : AnyObject] = [:]
-    private var rawString: String = ""
-    private var rawNumber: NSNumber = 0
-    private var rawNull: NSNull = NSNull()
-    /// Private type
-    private var _type: Type = .Null
-    /// prviate error
-    private var _error: NSError? = nil
-    
-    /// Object in JSON
-    public var object: AnyObject {
-        get {
-            switch self.type {
-            case .Array:
-                return self.rawArray
-            case .Dictionary:
-                return self.rawDictionary
-            case .String:
-                return self.rawString
-            case .Number:
-                return self.rawNumber
-            case .Bool:
-                return self.rawNumber
-            default:
-                return self.rawNull
-            }
-        }
-        set {
-            _error = nil
-            switch newValue {
-            case let number as NSNumber:
-                if number.isBool {
-                    _type = .Bool
-                } else {
-                    _type = .Number
-                }
-                self.rawNumber = number
-            case  let string as String:
-                _type = .String
-                self.rawString = string
-            case  _ as NSNull:
-                _type = .Null
-            case let array as [AnyObject]:
-                _type = .Array
-                self.rawArray = array
-            case let dictionary as [String : AnyObject]:
-                _type = .Dictionary
-                self.rawDictionary = dictionary
-            default:
-                _type = .Unknown
-                _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
-            }
-        }
-    }
-    
-    /// json type
-    public var type: Type { get { return _type } }
-    
-    /// Error in JSON
-    public var error: NSError? { get { return self._error } }
-    
-    /// The static null json
-    @available(*, unavailable, renamed="null")
-    public static var nullJSON: JSON { get { return null } }
-    public static var null: JSON { get { return JSON(NSNull()) } }
-}
-
-// MARK: - CollectionType, SequenceType, Indexable
-extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable {
-    
-    public typealias Generator = JSONGenerator
-    
-    public typealias Index = JSONIndex
-    
-    public var startIndex: JSON.Index {
-        switch self.type {
-        case .Array:
-            return JSONIndex(arrayIndex: self.rawArray.startIndex)
-        case .Dictionary:
-            return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex)
-        default:
-            return JSONIndex()
-        }
-    }
-    
-    public var endIndex: JSON.Index {
-        switch self.type {
-        case .Array:
-            return JSONIndex(arrayIndex: self.rawArray.endIndex)
-        case .Dictionary:
-            return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex)
-        default:
-            return JSONIndex()
-        }
-    }
-    
-    public subscript (position: JSON.Index) -> JSON.Generator.Element {
-        switch self.type {
-        case .Array:
-            return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!]))
-        case .Dictionary:
-            let (key, value) = self.rawDictionary[position.dictionaryIndex!]
-            return (key, JSON(value))
-        default:
-            return ("", JSON.null)
-        }
-    }
-    
-    /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `false`.
-    public var isEmpty: Bool {
-        get {
-            switch self.type {
-            case .Array:
-                return self.rawArray.isEmpty
-            case .Dictionary:
-                return self.rawDictionary.isEmpty
-            default:
-                return true
-            }
-        }
-    }
-    
-    /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`.
-    public var count: Int {
-        switch self.type {
-        case .Array:
-            return self.rawArray.count
-        case .Dictionary:
-            return self.rawDictionary.count
-        default:
-            return 0
-        }
-    }
-    
-    public func underestimateCount() -> Int {
-        switch self.type {
-        case .Array:
-            return self.rawArray.underestimateCount()
-        case .Dictionary:
-            return self.rawDictionary.underestimateCount()
-        default:
-            return 0
-        }
-    }
-    
-    /**
-    If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty.
-    
-    - returns: Return a *generator* over the elements of JSON.
-    */
-    public func generate() -> JSON.Generator {
-        return JSON.Generator(self)
-    }
-}
-
-public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable {
-    
-    let arrayIndex: Int?
-    let dictionaryIndex: DictionaryIndex<String, AnyObject>?
-    
-    let type: Type
-    
-    init(){
-        self.arrayIndex = nil
-        self.dictionaryIndex = nil
-        self.type = .Unknown
-    }
-    
-    init(arrayIndex: Int) {
-        self.arrayIndex = arrayIndex
-        self.dictionaryIndex = nil
-        self.type = .Array
-    }
-    
-    init(dictionaryIndex: DictionaryIndex<String, AnyObject>) {
-        self.arrayIndex = nil
-        self.dictionaryIndex = dictionaryIndex
-        self.type = .Dictionary
-    }
-    
-    public func successor() -> JSONIndex {
-        switch self.type {
-        case .Array:
-            return JSONIndex(arrayIndex: self.arrayIndex!.successor())
-        case .Dictionary:
-            return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor())
-        default:
-            return JSONIndex()
-        }
-    }
-}
-
-public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs.type, rhs.type) {
-    case (.Array, .Array):
-        return lhs.arrayIndex == rhs.arrayIndex
-    case (.Dictionary, .Dictionary):
-        return lhs.dictionaryIndex == rhs.dictionaryIndex
-    default:
-        return false
-    }
-}
-
-public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs.type, rhs.type) {
-    case (.Array, .Array):
-        return lhs.arrayIndex < rhs.arrayIndex
-    case (.Dictionary, .Dictionary):
-        return lhs.dictionaryIndex < rhs.dictionaryIndex
-    default:
-        return false
-    }
-}
-
-public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs.type, rhs.type) {
-    case (.Array, .Array):
-        return lhs.arrayIndex <= rhs.arrayIndex
-    case (.Dictionary, .Dictionary):
-        return lhs.dictionaryIndex <= rhs.dictionaryIndex
-    default:
-        return false
-    }
-}
-
-public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs.type, rhs.type) {
-    case (.Array, .Array):
-        return lhs.arrayIndex >= rhs.arrayIndex
-    case (.Dictionary, .Dictionary):
-        return lhs.dictionaryIndex >= rhs.dictionaryIndex
-    default:
-        return false
-    }
-}
-
-public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
-    switch (lhs.type, rhs.type) {
-    case (.Array, .Array):
-        return lhs.arrayIndex > rhs.arrayIndex
-    case (.Dictionary, .Dictionary):
-        return lhs.dictionaryIndex > rhs.dictionaryIndex
-    default:
-        return false
-    }
-}
-
-public struct JSONGenerator : GeneratorType {
-    
-    public typealias Element = (String, JSON)
-    
-    private let type: Type
-    private var dictionayGenerate: DictionaryGenerator<String, AnyObject>?
-    private var arrayGenerate: IndexingGenerator<[AnyObject]>?
-    private var arrayIndex: Int = 0
-    
-    init(_ json: JSON) {
-        self.type = json.type
-        if type == .Array {
-            self.arrayGenerate = json.rawArray.generate()
-        }else {
-            self.dictionayGenerate = json.rawDictionary.generate()
-        }
-    }
-    
-    public mutating func next() -> JSONGenerator.Element? {
-        switch self.type {
-        case .Array:
-            if let o = self.arrayGenerate!.next() {
-                return (String(self.arrayIndex++), JSON(o))
-            } else {
-                return nil
-            }
-        case .Dictionary:
-            if let (k, v): (String, AnyObject) = self.dictionayGenerate!.next() {
-                return (k, JSON(v))
-            } else {
-                return nil
-            }
-        default:
-            return nil
-        }
-    }
-}
-
-// MARK: - Subscript
-
-/**
-*  To mark both String and Int can be used in subscript.
-*/
-public protocol JSONSubscriptType {}
-
-extension Int: JSONSubscriptType {}
-
-extension String: JSONSubscriptType {}
-
-extension JSON {
-    
-    /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error.
-    private subscript(index index: Int) -> JSON {
-        get {
-            if self.type != .Array {
-                var r = JSON.null
-                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"])
-                return r
-            } else if index >= 0 && index < self.rawArray.count {
-                return JSON(self.rawArray[index])
-            } else {
-                var r = JSON.null
-                r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"])
-                return r
-            }
-        }
-        set {
-            if self.type == .Array {
-                if self.rawArray.count > index && newValue.error == nil {
-                    self.rawArray[index] = newValue.object
-                }
-            }
-        }
-    }
-    
-    /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error.
-    private subscript(key key: String) -> JSON {
-        get {
-            var r = JSON.null
-            if self.type == .Dictionary {
-                if let o = self.rawDictionary[key] {
-                    r = JSON(o)
-                } else {
-                    r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"])
-                }
-            } else {
-                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"])
-            }
-            return r
-        }
-        set {
-            if self.type == .Dictionary && newValue.error == nil {
-                self.rawDictionary[key] = newValue.object
-            }
-        }
-    }
-    
-    /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`,  return `subscript(key:)`.
-    private subscript(sub sub: JSONSubscriptType) -> JSON {
-        get {
-            if sub is String {
-                return self[key:sub as! String]
-            } else {
-                return self[index:sub as! Int]
-            }
-        }
-        set {
-            if sub is String {
-                self[key:sub as! String] = newValue
-            } else {
-                self[index:sub as! Int] = newValue
-            }
-        }
-    }
-    
-    /**
-    Find a json in the complex data structuresby using the Int/String's array.
-    
-    - parameter path: The target json's path. Example:
-    
-    let json = JSON[data]
-    let path = [9,"list","person","name"]
-    let name = json[path]
-    
-    The same as: let name = json[9]["list"]["person"]["name"]
-    
-    - returns: Return a json found by the path or a null json with error
-    */
-    public subscript(path: [JSONSubscriptType]) -> JSON {
-        get {
-            return path.reduce(self) { $0[sub: $1] }
-        }
-        set {
-            switch path.count {
-            case 0:
-                return
-            case 1:
-                self[sub:path[0]].object = newValue.object
-            default:
-                var aPath = path; aPath.removeAtIndex(0)
-                var nextJSON = self[sub: path[0]]
-                nextJSON[aPath] = newValue
-                self[sub: path[0]] = nextJSON
-            }
-        }
-    }
-    
-    /**
-    Find a json in the complex data structuresby using the Int/String's array.
-    
-    - parameter path: The target json's path. Example:
-    
-    let name = json[9,"list","person","name"]
-    
-    The same as: let name = json[9]["list"]["person"]["name"]
-    
-    - returns: Return a json found by the path or a null json with error
-    */
-    public subscript(path: JSONSubscriptType...) -> JSON {
-        get {
-            return self[path]
-        }
-        set {
-            self[path] = newValue
-        }
-    }
-}
-
-// MARK: - LiteralConvertible
-
-extension JSON: Swift.StringLiteralConvertible {
-    
-    public init(stringLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-    
-    public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-    
-    public init(unicodeScalarLiteral value: StringLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.IntegerLiteralConvertible {
-    
-    public init(integerLiteral value: IntegerLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.BooleanLiteralConvertible {
-    
-    public init(booleanLiteral value: BooleanLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.FloatLiteralConvertible {
-    
-    public init(floatLiteral value: FloatLiteralType) {
-        self.init(value)
-    }
-}
-
-extension JSON: Swift.DictionaryLiteralConvertible {
-    
-    public init(dictionaryLiteral elements: (String, AnyObject)...) {
-        self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in
-            var d = dictionary
-            d[element.0] = element.1
-            return d
-            })
-    }
-}
-
-extension JSON: Swift.ArrayLiteralConvertible {
-    
-    public init(arrayLiteral elements: AnyObject...) {
-        self.init(elements)
-    }
-}
-
-extension JSON: Swift.NilLiteralConvertible {
-    
-    public init(nilLiteral: ()) {
-        self.init(NSNull())
-    }
-}
-
-// MARK: - Raw
-
-extension JSON: Swift.RawRepresentable {
-    
-    public init?(rawValue: AnyObject) {
-        if JSON(rawValue).type == .Unknown {
-            return nil
-        } else {
-            self.init(rawValue)
-        }
-    }
-    
-    public var rawValue: AnyObject {
-        return self.object
-    }
-    
-    public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData {
-        guard NSJSONSerialization.isValidJSONObject(self.object) else {
-            throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"])
-        }
-        
-        return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt)
-    }
-    
-    public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? {
-        switch self.type {
-        case .Array, .Dictionary:
-            do {
-                let data = try self.rawData(options: opt)
-                return NSString(data: data, encoding: encoding) as? String
-            } catch _ {
-                return nil
-            }
-        case .String:
-            return self.rawString
-        case .Number:
-            return self.rawNumber.stringValue
-        case .Bool:
-            return self.rawNumber.boolValue.description
-        case .Null:
-            return "null"
-        default:
-            return nil
-        }
-    }
-}
-
-// MARK: - Printable, DebugPrintable
-
-extension JSON: Swift.Printable, Swift.DebugPrintable {
-    
-    public var description: String {
-        if let string = self.rawString(options:.PrettyPrinted) {
-            return string
-        } else {
-            return "unknown"
-        }
-    }
-    
-    public var debugDescription: String {
-        return description
-    }
-}
-
-// MARK: - Array
-
-extension JSON {
-    
-    //Optional [JSON]
-    public var array: [JSON]? {
-        get {
-            if self.type == .Array {
-                return self.rawArray.map{ JSON($0) }
-            } else {
-                return nil
-            }
-        }
-    }
-    
-    //Non-optional [JSON]
-    public var arrayValue: [JSON] {
-        get {
-            return self.array ?? []
-        }
-    }
-    
-    //Optional [AnyObject]
-    public var arrayObject: [AnyObject]? {
-        get {
-            switch self.type {
-            case .Array:
-                return self.rawArray
-            default:
-                return nil
-            }
-        }
-        set {
-            if let array = newValue {
-                self.object = array
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-}
-
-// MARK: - Dictionary
-
-extension JSON {
-    
-    //Optional [String : JSON]
-    public var dictionary: [String : JSON]? {
-        if self.type == .Dictionary {
-            return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in
-                var d = dictionary
-                d[element.0] = JSON(element.1)
-                return d
-            }
-        } else {
-            return nil
-        }
-    }
-    
-    //Non-optional [String : JSON]
-    public var dictionaryValue: [String : JSON] {
-        return self.dictionary ?? [:]
-    }
-    
-    //Optional [String : AnyObject]
-    public var dictionaryObject: [String : AnyObject]? {
-        get {
-            switch self.type {
-            case .Dictionary:
-                return self.rawDictionary
-            default:
-                return nil
-            }
-        }
-        set {
-            if let v = newValue {
-                self.object = v
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-}
-
-// MARK: - Bool
-
-extension JSON: Swift.BooleanType {
-    
-    //Optional bool
-    public var bool: Bool? {
-        get {
-            switch self.type {
-            case .Bool:
-                return self.rawNumber.boolValue
-            default:
-                return nil
-            }
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(bool: newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    //Non-optional bool
-    public var boolValue: Bool {
-        get {
-            switch self.type {
-            case .Bool, .Number, .String:
-                return self.object.boolValue
-            default:
-                return false
-            }
-        }
-        set {
-            self.object = NSNumber(bool: newValue)
-        }
-    }
-}
-
-// MARK: - String
-
-extension JSON {
-    
-    //Optional string
-    public var string: String? {
-        get {
-            switch self.type {
-            case .String:
-                return self.object as? String
-            default:
-                return nil
-            }
-        }
-        set {
-            if newValue != nil {
-                self.object = NSString(string:newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    //Non-optional string
-    public var stringValue: String {
-        get {
-            switch self.type {
-            case .String:
-                return self.object as! String
-            case .Number:
-                return self.object.stringValue
-            case .Bool:
-                return (self.object as! Bool).description
-            default:
-                return ""
-            }
-        }
-        set {
-            self.object = NSString(string:newValue)
-        }
-    }
-}
-
-// MARK: - Number
-extension JSON {
-    
-    //Optional number
-    public var number: NSNumber? {
-        get {
-            switch self.type {
-            case .Number, .Bool:
-                return self.rawNumber
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = newValue ?? NSNull()
-        }
-    }
-    
-    //Non-optional number
-    public var numberValue: NSNumber {
-        get {
-            switch self.type {
-            case .String:
-                let decimal = NSDecimalNumber(string: self.object as? String)
-                if decimal == NSDecimalNumber.notANumber() {  // indicates parse error
-                    return NSDecimalNumber.zero()
-                }
-                return decimal
-            case .Number, .Bool:
-                return self.object as! NSNumber
-            default:
-                return NSNumber(double: 0.0)
-            }
-        }
-        set {
-            self.object = newValue
-        }
-    }
-}
-
-//MARK: - Null
-extension JSON {
-    
-    public var null: NSNull? {
-        get {
-            switch self.type {
-            case .Null:
-                return self.rawNull
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = NSNull()
-        }
-    }
-    public func isExists() -> Bool{
-        if let errorValue = error where errorValue.code == ErrorNotExist{
-            return false
-        }
-        return true
-    }
-}
-
-//MARK: - URL
-extension JSON {
-    
-    //Optional URL
-    public var URL: NSURL? {
-        get {
-            switch self.type {
-            case .String:
-                if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
-                    return NSURL(string: encodedString_)
-                } else {
-                    return nil
-                }
-            default:
-                return nil
-            }
-        }
-        set {
-            self.object = newValue?.absoluteString ?? NSNull()
-        }
-    }
-}
-
-// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64
-
-extension JSON {
-    
-    public var double: Double? {
-        get {
-            return self.number?.doubleValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(double: newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    public var doubleValue: Double {
-        get {
-            return self.numberValue.doubleValue
-        }
-        set {
-            self.object = NSNumber(double: newValue)
-        }
-    }
-    
-    public var float: Float? {
-        get {
-            return self.number?.floatValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(float: newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    public var floatValue: Float {
-        get {
-            return self.numberValue.floatValue
-        }
-        set {
-            self.object = NSNumber(float: newValue)
-        }
-    }
-    
-    public var int: Int? {
-        get {
-            return self.number?.longValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(integer: newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    public var intValue: Int {
-        get {
-            return self.numberValue.integerValue
-        }
-        set {
-            self.object = NSNumber(integer: newValue)
-        }
-    }
-    
-    public var uInt: UInt? {
-        get {
-            return self.number?.unsignedLongValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(unsignedLong: newValue!)
-            } else {
-                self.object = NSNull()
-            }
-        }
-    }
-    
-    public var uIntValue: UInt {
-        get {
-            return self.numberValue.unsignedLongValue
-        }
-        set {
-            self.object = NSNumber(unsignedLong: newValue)
-        }
-    }
-    
-    public var int8: Int8? {
-        get {
-            return self.number?.charValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(char: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var int8Value: Int8 {
-        get {
-            return self.numberValue.charValue
-        }
-        set {
-            self.object = NSNumber(char: newValue)
-        }
-    }
-    
-    public var uInt8: UInt8? {
-        get {
-            return self.number?.unsignedCharValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(unsignedChar: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var uInt8Value: UInt8 {
-        get {
-            return self.numberValue.unsignedCharValue
-        }
-        set {
-            self.object = NSNumber(unsignedChar: newValue)
-        }
-    }
-    
-    public var int16: Int16? {
-        get {
-            return self.number?.shortValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(short: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var int16Value: Int16 {
-        get {
-            return self.numberValue.shortValue
-        }
-        set {
-            self.object = NSNumber(short: newValue)
-        }
-    }
-    
-    public var uInt16: UInt16? {
-        get {
-            return self.number?.unsignedShortValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(unsignedShort: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var uInt16Value: UInt16 {
-        get {
-            return self.numberValue.unsignedShortValue
-        }
-        set {
-            self.object = NSNumber(unsignedShort: newValue)
-        }
-    }
-    
-    public var int32: Int32? {
-        get {
-            return self.number?.intValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(int: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var int32Value: Int32 {
-        get {
-            return self.numberValue.intValue
-        }
-        set {
-            self.object = NSNumber(int: newValue)
-        }
-    }
-    
-    public var uInt32: UInt32? {
-        get {
-            return self.number?.unsignedIntValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(unsignedInt: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var uInt32Value: UInt32 {
-        get {
-            return self.numberValue.unsignedIntValue
-        }
-        set {
-            self.object = NSNumber(unsignedInt: newValue)
-        }
-    }
-    
-    public var int64: Int64? {
-        get {
-            return self.number?.longLongValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(longLong: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var int64Value: Int64 {
-        get {
-            return self.numberValue.longLongValue
-        }
-        set {
-            self.object = NSNumber(longLong: newValue)
-        }
-    }
-    
-    public var uInt64: UInt64? {
-        get {
-            return self.number?.unsignedLongLongValue
-        }
-        set {
-            if newValue != nil {
-                self.object = NSNumber(unsignedLongLong: newValue!)
-            } else {
-                self.object =  NSNull()
-            }
-        }
-    }
-    
-    public var uInt64Value: UInt64 {
-        get {
-            return self.numberValue.unsignedLongLongValue
-        }
-        set {
-            self.object = NSNumber(unsignedLongLong: newValue)
-        }
-    }
-}
-
-//MARK: - Comparable
-extension JSON : Swift.Comparable {}
-
-public func ==(lhs: JSON, rhs: JSON) -> Bool {
-    
-    switch (lhs.type, rhs.type) {
-    case (.Number, .Number):
-        return lhs.rawNumber == rhs.rawNumber
-    case (.String, .String):
-        return lhs.rawString == rhs.rawString
-    case (.Bool, .Bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.Array, .Array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.Dictionary, .Dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.Null, .Null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func <=(lhs: JSON, rhs: JSON) -> Bool {
-    
-    switch (lhs.type, rhs.type) {
-    case (.Number, .Number):
-        return lhs.rawNumber <= rhs.rawNumber
-    case (.String, .String):
-        return lhs.rawString <= rhs.rawString
-    case (.Bool, .Bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.Array, .Array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.Dictionary, .Dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.Null, .Null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func >=(lhs: JSON, rhs: JSON) -> Bool {
-    
-    switch (lhs.type, rhs.type) {
-    case (.Number, .Number):
-        return lhs.rawNumber >= rhs.rawNumber
-    case (.String, .String):
-        return lhs.rawString >= rhs.rawString
-    case (.Bool, .Bool):
-        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
-    case (.Array, .Array):
-        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
-    case (.Dictionary, .Dictionary):
-        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
-    case (.Null, .Null):
-        return true
-    default:
-        return false
-    }
-}
-
-public func >(lhs: JSON, rhs: JSON) -> Bool {
-    
-    switch (lhs.type, rhs.type) {
-    case (.Number, .Number):
-        return lhs.rawNumber > rhs.rawNumber
-    case (.String, .String):
-        return lhs.rawString > rhs.rawString
-    default:
-        return false
-    }
-}
-
-public func <(lhs: JSON, rhs: JSON) -> Bool {
-    
-    switch (lhs.type, rhs.type) {
-    case (.Number, .Number):
-        return lhs.rawNumber < rhs.rawNumber
-    case (.String, .String):
-        return lhs.rawString < rhs.rawString
-    default:
-        return false
-    }
-}
-
-private let trueNumber = NSNumber(bool: true)
-private let falseNumber = NSNumber(bool: false)
-private let trueObjCType = String.fromCString(trueNumber.objCType)
-private let falseObjCType = String.fromCString(falseNumber.objCType)
-
-// MARK: - NSNumber: Comparable
-
-extension NSNumber {
-    var isBool:Bool {
-        get {
-            let objCType = String.fromCString(self.objCType)
-            if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType)
-                || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){
-                    return true
-            } else {
-                return false
-            }
-        }
-    }
-}
-
-public func ==(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == NSComparisonResult.OrderedSame
-    }
-}
-
-public func !=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    return !(lhs == rhs)
-}
-
-public func <(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
-    }
-}
-
-public func >(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) == NSComparisonResult.OrderedDescending
-    }
-}
-
-public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) != NSComparisonResult.OrderedDescending
-    }
-}
-
-public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool {
-    
-    switch (lhs.isBool, rhs.isBool) {
-    case (false, true):
-        return false
-    case (true, false):
-        return false
-    default:
-        return lhs.compare(rhs) != NSComparisonResult.OrderedAscending
-    }
-}

+ 0 - 30
SwifterSampleOSX/main.swift

@@ -1,30 +0,0 @@
-//
-//  main.swift
-//  SwifterOSX
-//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-import Swifter
-
-
-do {
-    let server = demoServer(try File.currentWorkingDirectory())
-    server["/SwiftyJSON"] = { request in
-        let js: JSON = ["return": "OK", "isItAJSON": true, "code" : 200]
-        return .OK(.Custom(js, { object in
-            guard let obj = object as? JSON, let rawString = obj.rawString() else {
-                throw SerializationError.InvalidObject
-            }
-            return rawString
-        }))
-    }
-    server["/testAfterBaseRoute"] = { request in
-        return .OK(.Html("ok !"))
-    }
-    try server.start(9080, forceIPv4: true)
-    print("Server has started ( port = 9080 ). Try to connect now...")
-    NSRunLoop.mainRunLoop().run()
-} catch {
-    print("Server start error: \(error)")
-}

+ 0 - 0
Resources/logo.png → XCode/Resources/logo.png


+ 362 - 148
Swifter.xcodeproj/project.pbxproj → XCode/Swifter.xcodeproj/project.pbxproj

@@ -7,62 +7,104 @@
 	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 */; };
+		0858E7F81D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
+		0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
+		2659FC1A1DADC077003F3930 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.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 */; };
+		269B478B1D3AAAE20042D137 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
+		269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
+		269B478D1D3AAAE20042D137 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
+		269B478E1D3AAAE20042D137 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
+		269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
+		269B47901D3AAAE20042D137 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
+		269B47921D3AAAE20042D137 /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
+		269B47931D3AAAE20042D137 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
+		269B47941D3AAAE20042D137 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
+		269B47951D3AAAE20042D137 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
+		269B47961D3AAAE20042D137 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
+		269B47971D3AAAE20042D137 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
+		269B47981D3AAAE20042D137 /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
+		269B47991D3AAAE20042D137 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		269B47A71D3AAC4F0042D137 /* SwiftertvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 269B47A51D3AAC4F0042D137 /* SwiftertvOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893EA1C05127900A29F63 /* SwifteriOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893E91C05127900A29F63 /* SwifteriOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893FE1C0512C400A29F63 /* SwifterMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893FD1C0512C400A29F63 /* SwifterMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */; };
-		7C1A2BFB1C5605F50026D3BF /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1A2BFA1C5605F50026D3BF /* String+BASE64.swift */; };
-		7C1A2BFC1C5605F50026D3BF /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1A2BFA1C5605F50026D3BF /* String+BASE64.swift */; };
-		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 */; };
+		7C377E181D964B96009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
+		7C377E191D964B9F009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
+		7C458EFC1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
+		7C458EFD1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
+		7C458EFE1D4A7526006A68E5 /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.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 */; };
 		7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */; };
-		7C73C6AA1C261A2100AEF6CA /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6941C2619E100AEF6CA /* DemoServer.swift */; };
-		7C73C6AB1C261A2100AEF6CA /* HttpHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */; };
-		7C73C6AC1C261A2100AEF6CA /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6961C2619E100AEF6CA /* HttpParser.swift */; };
-		7C73C6AD1C261A2100AEF6CA /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6971C2619E100AEF6CA /* HttpRequest.swift */; };
-		7C73C6AE1C261A2100AEF6CA /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6981C2619E100AEF6CA /* HttpResponse.swift */; };
-		7C73C6AF1C261A2100AEF6CA /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6991C2619E100AEF6CA /* HttpRouter.swift */; };
-		7C73C6B01C261A2100AEF6CA /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69A1C2619E100AEF6CA /* HttpServer.swift */; };
-		7C73C6B11C261A2100AEF6CA /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */; };
-		7C73C6B21C261A2100AEF6CA /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69C1C2619E100AEF6CA /* Socket.swift */; };
-		7C73C6B31C261A2100AEF6CA /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69D1C2619E100AEF6CA /* String+Misc.swift */; };
-		7C73C6B51C261A2600AEF6CA /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6941C2619E100AEF6CA /* DemoServer.swift */; };
-		7C73C6B61C261A2600AEF6CA /* HttpHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */; };
-		7C73C6B71C261A2600AEF6CA /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6961C2619E100AEF6CA /* HttpParser.swift */; };
-		7C73C6B81C261A2600AEF6CA /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6971C2619E100AEF6CA /* HttpRequest.swift */; };
-		7C73C6B91C261A2600AEF6CA /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6981C2619E100AEF6CA /* HttpResponse.swift */; };
-		7C73C6BA1C261A2600AEF6CA /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C6991C2619E100AEF6CA /* HttpRouter.swift */; };
-		7C73C6BB1C261A2600AEF6CA /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69A1C2619E100AEF6CA /* HttpServer.swift */; };
-		7C73C6BC1C261A2600AEF6CA /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */; };
-		7C73C6BD1C261A2600AEF6CA /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69C1C2619E100AEF6CA /* Socket.swift */; };
-		7C73C6BE1C261A2600AEF6CA /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C73C69D1C2619E100AEF6CA /* String+Misc.swift */; };
-		7C7488781C1DA07300CBCD77 /* file.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
-		7C74887B1C1DA08200CBCD77 /* file.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
+		7C76B29F1D369BEC00D35BFB /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
+		7C76B2A01D369BEC00D35BFB /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
+		7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
+		7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
+		7C76B70D1D2C456A0030FC98 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
+		7C76B70E1D2C456B0030FC98 /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */; };
+		7C76B7111D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
+		7C76B7121D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
+		7C76B7131D2C45730030FC98 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
+		7C76B7141D2C45730030FC98 /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
+		7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
+		7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
+		7C76B7171D2C45780030FC98 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
+		7C76B7181D2C45790030FC98 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
+		7C76B7191D2C457C0030FC98 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
+		7C76B71A1D2C457C0030FC98 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
+		7C76B71B1D2C457E0030FC98 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
+		7C76B71C1D2C457E0030FC98 /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
+		7C76B71D1D2C45820030FC98 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
+		7C76B71E1D2C45820030FC98 /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
+		7C76B71F1D2C45840030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
+		7C76B7201D2C45840030FC98 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
+		7C76B7211D2C45870030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
+		7C76B7221D2C45870030FC98 /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
+		7C76B7231D2C45890030FC98 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
+		7C76B7241D2C458A0030FC98 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
+		7C76B7251D2C458C0030FC98 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		7C76B7261D2C458D0030FC98 /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		7C76B7271D2C458F0030FC98 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
+		7C76B7281D2C458F0030FC98 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
+		7C76B7291D2C45920030FC98 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
+		7C76B72A1D2C45920030FC98 /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
+		7C76B72B1D2C45940030FC98 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
+		7C76B72C1D2C45950030FC98 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
-		7CA4815819A2EF2B0030B30D /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = 7CA4815719A2EF2B0030B30D /* test.json */; };
-		7CA4815919A2EF560030B30D /* test.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CA4815719A2EF2B0030B30D /* test.json */; };
 		7CB102E01A17381D00CBA3B4 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
-		7CC0F8C91C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */; };
-		7CC0F8CA1C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */; };
-		7CC0F8CC1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */; };
-		7CC0F8CD1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */; };
+		7CCB8C5E1D97B852008B9712 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
+		7CCB8C621D97B8E9008B9712 /* SwifterTestsHttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */; };
 		7CCD87611C66099B0068099B /* Swifter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AE893E71C05127900A29F63 /* Swifter.framework */; };
 		7CCD87701C660B250068099B /* SwifterTestsHttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */; };
 		7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */; };
-		7CCD877E1C660EA30068099B /* Swifter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AE893FB1C0512C400A29F63 /* Swifter.framework */; };
 		7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */; };
 		7CCD87851C660ED60068099B /* SwifterTestsStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */; };
-		7CCD87871C676EE50068099B /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD87861C676EE50068099B /* File.swift */; };
-		7CCD87881C676EE50068099B /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD87861C676EE50068099B /* File.swift */; };
 		7CDAB8131BE2A1D400C8A977 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7CDAB80D1BE2A1D400C8A977 /* Main.storyboard */; };
 		7CDAB8141BE2A1D400C8A977 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7CDAB80F1BE2A1D400C8A977 /* Images.xcassets */; };
 		7CDAB8161BE2A1D400C8A977 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB8111BE2A1D400C8A977 /* ViewController.swift */; };
-		98630C071A1C9A9D00478D08 /* login.html in Resources */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
+		7CEBB86F1D94612D00370A6B /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
+		7CEBB8701D94612D00370A6B /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */; };
+		7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
+		7CEBB8721D94612D00370A6B /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */; };
+		7CEBB8731D94612D00370A6B /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */; };
+		7CEBB8741D94612D00370A6B /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* HttpServer.swift */; };
+		7CEBB8751D94612D00370A6B /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */; };
+		7CEBB8761D94612D00370A6B /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F31D2C44F30030FC98 /* Process.swift */; };
+		7CEBB8771D94612D00370A6B /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Scopes.swift */; };
+		7CEBB8781D94612D00370A6B /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F51D2C44F30030FC98 /* Socket.swift */; };
+		7CEBB8791D94612D00370A6B /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B29E1D369BEC00D35BFB /* Socket+File.swift */; };
+		7CEBB87A1D94612D00370A6B /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */; };
+		7CEBB87B1D94612D00370A6B /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */; };
+		7CEBB87C1D94612D00370A6B /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
+		7CEBB87D1D94612D00370A6B /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */; };
+		7CEBB87E1D94612D00370A6B /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F91D2C44F30030FC98 /* WebSockets.swift */; };
+		7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -87,13 +129,6 @@
 			remoteGlobalIDString = 7AE893E61C05127900A29F63;
 			remoteInfo = SwifteriOS;
 		};
-		7CCD877F1C660EA30068099B /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = 7C839B6619422CFF003A6950 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 7AE893FA1C0512C400A29F63;
-			remoteInfo = SwifterMac;
-		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -103,17 +138,18 @@
 			dstPath = "";
 			dstSubfolderSpec = 7;
 			files = (
-				7C74887B1C1DA08200CBCD77 /* file.html in CopyFiles */,
 				7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */,
-				7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */,
-				7CA4815919A2EF560030B30D /* test.json in CopyFiles */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		18E610A51BD6397D00B7D17A /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = "<group>"; };
+		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>"; };
 		7AE893E71C05127900A29F63 /* Swifter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swifter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7AE893E91C05127900A29F63 /* SwifteriOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwifteriOS.h; sourceTree = "<group>"; };
 		7AE893EB1C05127900A29F63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -121,44 +157,52 @@
 		7AE893FD1C0512C400A29F63 /* SwifterMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwifterMac.h; sourceTree = "<group>"; };
 		7AE893FF1C0512C400A29F63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		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>"; };
+		7C377E161D964B6A009C6148 /* String+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+File.swift"; sourceTree = "<group>"; };
+		7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+Server.swift"; sourceTree = "<group>"; };
 		7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsWebSocketSession.swift; sourceTree = "<group>"; };
-		7C73C6941C2619E100AEF6CA /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = "<group>"; };
-		7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpHandlers.swift; sourceTree = "<group>"; };
-		7C73C6961C2619E100AEF6CA /* HttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpParser.swift; sourceTree = "<group>"; };
-		7C73C6971C2619E100AEF6CA /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
-		7C73C6981C2619E100AEF6CA /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = "<group>"; };
-		7C73C6991C2619E100AEF6CA /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = "<group>"; };
-		7C73C69A1C2619E100AEF6CA /* HttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServer.swift; sourceTree = "<group>"; };
-		7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerIO.swift; sourceTree = "<group>"; };
-		7C73C69C1C2619E100AEF6CA /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = "<group>"; };
-		7C73C69D1C2619E100AEF6CA /* String+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = "<group>"; };
-		7C7488771C1DA07300CBCD77 /* file.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = file.html; sourceTree = "<group>"; };
+		7C76B29E1D369BEC00D35BFB /* Socket+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+File.swift"; sourceTree = "<group>"; };
+		7C76B2A11D369C9D00D35BFB /* Errno.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errno.swift; sourceTree = "<group>"; };
+		7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = "<group>"; };
+		7C76B6EC1D2C44F30030FC98 /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = "<group>"; };
+		7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpParser.swift; sourceTree = "<group>"; };
+		7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
+		7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = "<group>"; };
+		7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = "<group>"; };
+		7C76B6F11D2C44F30030FC98 /* HttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServer.swift; sourceTree = "<group>"; };
+		7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerIO.swift; sourceTree = "<group>"; };
+		7C76B6F31D2C44F30030FC98 /* Process.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = "<group>"; };
+		7C76B6F41D2C44F30030FC98 /* Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scopes.swift; sourceTree = "<group>"; };
+		7C76B6F51D2C44F30030FC98 /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = "<group>"; };
+		7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BASE64.swift"; sourceTree = "<group>"; };
+		7C76B6F71D2C44F30030FC98 /* String+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = "<group>"; };
+		7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SHA1.swift"; sourceTree = "<group>"; };
+		7C76B6F91D2C44F30030FC98 /* WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSockets.swift; sourceTree = "<group>"; };
 		7C839B6E19422CFF003A6950 /* SwifterSampleiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwifterSampleiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813B19A2EA8D0030B30D /* SwifterSampleOSX */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwifterSampleOSX; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CA4813D19A2EA8D0030B30D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
-		7CA4815719A2EF2B0030B30D /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = "<group>"; };
 		7CB102DF1A17381D00CBA3B4 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
-		7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HttpHandlers+Files.swift"; sourceTree = "<group>"; };
-		7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HttpHandlers+WebSockets.swift"; sourceTree = "<group>"; };
+		7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsHttpRouter.swift; sourceTree = "<group>"; };
 		7CCD875C1C66099B0068099B /* SwifteriOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifteriOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CCD87601C66099B0068099B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsHttpParser.swift; sourceTree = "<group>"; };
 		7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsStringExtensions.swift; sourceTree = "<group>"; };
 		7CCD87791C660EA30068099B /* SwifterOSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifterOSXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		7CCD877D1C660EA30068099B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		7CCD87861C676EE50068099B /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
 		7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		7CDAB80E1BE2A1D400C8A977 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		7CDAB80F1BE2A1D400C8A977 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
 		7CDAB8101BE2A1D400C8A977 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7CDAB8111BE2A1D400C8A977 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
-		7CEAF86F1C14B2B5003252DE /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
-		98630C061A1C9A9D00478D08 /* login.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = login.html; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+		269B479A1D3AAAE20042D137 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		7AE893E31C05127900A29F63 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -199,13 +243,21 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7CCD877E1C660EA30068099B /* Swifter.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		269B47A31D3AAC090042D137 /* SwiftertvOS */ = {
+			isa = PBXGroup;
+			children = (
+				269B47A41D3AAC4F0042D137 /* Info.plist */,
+				269B47A51D3AAC4F0042D137 /* SwiftertvOS.h */,
+			);
+			path = SwiftertvOS;
+			sourceTree = "<group>";
+		};
 		7AE893E81C05127900A29F63 /* SwifteriOS */ = {
 			isa = PBXGroup;
 			children = (
@@ -224,17 +276,44 @@
 			path = SwifterMac;
 			sourceTree = "<group>";
 		};
+		7C76B6E91D2C44F30030FC98 /* Sources */ = {
+			isa = PBXGroup;
+			children = (
+				7C76B6EA1D2C44F30030FC98 /* DemoServer.swift */,
+				7C76B6EC1D2C44F30030FC98 /* Files.swift */,
+				7C76B6ED1D2C44F30030FC98 /* HttpParser.swift */,
+				7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */,
+				7C76B6EF1D2C44F30030FC98 /* HttpResponse.swift */,
+				7C76B6F01D2C44F30030FC98 /* HttpRouter.swift */,
+				7C76B6F11D2C44F30030FC98 /* HttpServer.swift */,
+				7C76B6F21D2C44F30030FC98 /* HttpServerIO.swift */,
+				7C76B6F31D2C44F30030FC98 /* Process.swift */,
+				7C76B6F41D2C44F30030FC98 /* Scopes.swift */,
+				7C76B6F51D2C44F30030FC98 /* Socket.swift */,
+				7C76B29E1D369BEC00D35BFB /* Socket+File.swift */,
+				7C458EFB1D4A7526006A68E5 /* Socket+Server.swift */,
+				7C76B6F61D2C44F30030FC98 /* String+BASE64.swift */,
+				7C76B6F71D2C44F30030FC98 /* String+Misc.swift */,
+				7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */,
+				7C76B6F91D2C44F30030FC98 /* WebSockets.swift */,
+				7C76B2A11D369C9D00D35BFB /* Errno.swift */,
+				7C377E161D964B6A009C6148 /* String+File.swift */,
+			);
+			name = Sources;
+			path = ../Sources;
+			sourceTree = "<group>";
+		};
 		7C839B6519422CFF003A6950 = {
 			isa = PBXGroup;
 			children = (
-				7CEAF86F1C14B2B5003252DE /* Package.swift */,
+				7C76B6E91D2C44F30030FC98 /* Sources */,
 				7CA4815619A2EF2B0030B30D /* Resources */,
-				7CEAF84A1C14B29B003252DE /* Sources */,
 				7CCD876C1C660B250068099B /* SwifterTestsCommon */,
 				7CCD875D1C66099B0068099B /* SwifteriOSTests */,
 				7CCD877A1C660EA30068099B /* SwifterOSXTests */,
 				7C839B6F19422CFF003A6950 /* Products */,
 				7AE893FC1C0512C400A29F63 /* SwifterMac */,
+				269B47A31D3AAC090042D137 /* SwiftertvOS */,
 				7AE893E81C05127900A29F63 /* SwifteriOS */,
 				7CA4813C19A2EA8D0030B30D /* SwifterSampleOSX */,
 				7CDAB80B1BE2A1D400C8A977 /* SwifterSampleiOS */,
@@ -250,6 +329,7 @@
 				7AE893FB1C0512C400A29F63 /* Swifter.framework */,
 				7CCD875C1C66099B0068099B /* SwifteriOSTests.xctest */,
 				7CCD87791C660EA30068099B /* SwifterOSXTests.xctest */,
+				269B47A11D3AAAE20042D137 /* Swifter.framework */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -257,7 +337,6 @@
 		7CA4813C19A2EA8D0030B30D /* SwifterSampleOSX */ = {
 			isa = PBXGroup;
 			children = (
-				18E610A51BD6397D00B7D17A /* SwiftyJSON.swift */,
 				7CA4813D19A2EA8D0030B30D /* main.swift */,
 			);
 			path = SwifterSampleOSX;
@@ -266,10 +345,7 @@
 		7CA4815619A2EF2B0030B30D /* Resources */ = {
 			isa = PBXGroup;
 			children = (
-				7C7488771C1DA07300CBCD77 /* file.html */,
-				98630C061A1C9A9D00478D08 /* login.html */,
 				7CB102DF1A17381D00CBA3B4 /* logo.png */,
-				7CA4815719A2EF2B0030B30D /* test.json */,
 			);
 			path = Resources;
 			sourceTree = "<group>";
@@ -285,9 +361,12 @@
 		7CCD876C1C660B250068099B /* SwifterTestsCommon */ = {
 			isa = PBXGroup;
 			children = (
+				7CCB8C5F1D97B8CC008B9712 /* SwifterTestsHttpRouter.swift */,
 				7CCD876D1C660B250068099B /* SwifterTestsHttpParser.swift */,
+				0858E7F61D68BC2600491CD1 /* PingServer.swift */,
 				7CCD876E1C660B250068099B /* SwifterTestsStringExtensions.swift */,
 				7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */,
+				0858E7F31D68BB2600491CD1 /* IOSafetyTests.swift */,
 			);
 			path = SwifterTestsCommon;
 			sourceTree = "<group>";
@@ -313,31 +392,17 @@
 			path = SwifterSampleiOS;
 			sourceTree = "<group>";
 		};
-		7CEAF84A1C14B29B003252DE /* Sources */ = {
-			isa = PBXGroup;
-			children = (
-				7C73C6941C2619E100AEF6CA /* DemoServer.swift */,
-				7CC0F8C81C50136B00B65A94 /* HttpHandlers+Files.swift */,
-				7CC0F8CB1C5014A200B65A94 /* HttpHandlers+WebSockets.swift */,
-				7C73C6951C2619E100AEF6CA /* HttpHandlers.swift */,
-				7C73C6961C2619E100AEF6CA /* HttpParser.swift */,
-				7C73C6971C2619E100AEF6CA /* HttpRequest.swift */,
-				7C73C6981C2619E100AEF6CA /* HttpResponse.swift */,
-				7C73C6991C2619E100AEF6CA /* HttpRouter.swift */,
-				7C73C69A1C2619E100AEF6CA /* HttpServer.swift */,
-				7C73C69B1C2619E100AEF6CA /* HttpServerIO.swift */,
-				7C73C69C1C2619E100AEF6CA /* Socket.swift */,
-				7CCD87861C676EE50068099B /* File.swift */,
-				7C73C69D1C2619E100AEF6CA /* String+Misc.swift */,
-				7C2BEC771C518B7C00B8EE90 /* String+SHA1.swift */,
-				7C1A2BFA1C5605F50026D3BF /* String+BASE64.swift */,
-			);
-			path = Sources;
-			sourceTree = "<group>";
-		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
+		269B479B1D3AAAE20042D137 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				269B47A71D3AAC4F0042D137 /* SwiftertvOS.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		7AE893E41C05127900A29F63 /* Headers */ = {
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
@@ -357,6 +422,24 @@
 /* End PBXHeadersBuildPhase section */
 
 /* Begin PBXNativeTarget section */
+		269B47861D3AAAE20042D137 /* SwiftertvOS */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 269B479E1D3AAAE20042D137 /* Build configuration list for PBXNativeTarget "SwiftertvOS" */;
+			buildPhases = (
+				269B47871D3AAAE20042D137 /* Sources */,
+				269B479A1D3AAAE20042D137 /* Frameworks */,
+				269B479B1D3AAAE20042D137 /* Headers */,
+				269B479D1D3AAAE20042D137 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = SwiftertvOS;
+			productName = SwifteriOS;
+			productReference = 269B47A11D3AAAE20042D137 /* Swifter.framework */;
+			productType = "com.apple.product-type.framework";
+		};
 		7AE893E61C05127900A29F63 /* SwifteriOS */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = 7AE893EC1C05127900A29F63 /* Build configuration list for PBXNativeTarget "SwifteriOS" */;
@@ -458,7 +541,6 @@
 			buildRules = (
 			);
 			dependencies = (
-				7CCD87801C660EA30068099B /* PBXTargetDependency */,
 			);
 			name = SwifterOSXTests;
 			productName = SwifterOSXTests;
@@ -472,11 +554,12 @@
 			isa = PBXProject;
 			attributes = {
 				LastSwiftUpdateCheck = 0720;
-				LastUpgradeCheck = 0730;
+				LastUpgradeCheck = 0800;
 				ORGANIZATIONNAME = "Damian Kołakowski";
 				TargetAttributes = {
 					7AE893E61C05127900A29F63 = {
 						CreatedOnToolsVersion = 7.1;
+						LastSwiftMigration = 0800;
 					};
 					7AE893FA1C0512C400A29F63 = {
 						CreatedOnToolsVersion = 7.1;
@@ -512,6 +595,7 @@
 				7CA4813A19A2EA8D0030B30D /* SwifterSampleOSX */,
 				7AE893E61C05127900A29F63 /* SwifteriOS */,
 				7AE893FA1C0512C400A29F63 /* SwifterMac */,
+				269B47861D3AAAE20042D137 /* SwiftertvOS */,
 				7CCD875B1C66099B0068099B /* SwifteriOSTests */,
 				7CCD87781C660EA30068099B /* SwifterOSXTests */,
 			);
@@ -519,6 +603,13 @@
 /* End PBXProject section */
 
 /* Begin PBXResourcesBuildPhase section */
+		269B479D1D3AAAE20042D137 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		7AE893E51C05127900A29F63 /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -537,12 +628,9 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7C7488781C1DA07300CBCD77 /* file.html in Resources */,
 				7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */,
 				7CB102E01A17381D00CBA3B4 /* logo.png in Resources */,
 				7CDAB8141BE2A1D400C8A977 /* Images.xcassets in Resources */,
-				98630C071A1C9A9D00478D08 /* login.html in Resources */,
-				7CA4815819A2EF2B0030B30D /* test.json in Resources */,
 				7CDAB8131BE2A1D400C8A977 /* Main.storyboard in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -564,25 +652,55 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
+		269B47871D3AAAE20042D137 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				269B47881D3AAAE20042D137 /* HttpResponse.swift in Sources */,
+				269B47891D3AAAE20042D137 /* Scopes.swift in Sources */,
+				269B478A1D3AAAE20042D137 /* Process.swift in Sources */,
+				269B478B1D3AAAE20042D137 /* HttpParser.swift in Sources */,
+				269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */,
+				269B478D1D3AAAE20042D137 /* WebSockets.swift in Sources */,
+				269B478E1D3AAAE20042D137 /* HttpServer.swift in Sources */,
+				269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */,
+				269B47901D3AAAE20042D137 /* DemoServer.swift in Sources */,
+				269B47921D3AAAE20042D137 /* Socket+File.swift in Sources */,
+				269B47931D3AAAE20042D137 /* Socket.swift in Sources */,
+				269B47941D3AAAE20042D137 /* HttpServerIO.swift in Sources */,
+				269B47951D3AAAE20042D137 /* Files.swift in Sources */,
+				2659FC1A1DADC077003F3930 /* String+File.swift in Sources */,
+				269B47961D3AAAE20042D137 /* HttpRouter.swift in Sources */,
+				269B47971D3AAAE20042D137 /* String+SHA1.swift in Sources */,
+				7C458EFE1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				269B47981D3AAAE20042D137 /* Errno.swift in Sources */,
+				269B47991D3AAAE20042D137 /* String+BASE64.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		7AE893E21C05127900A29F63 /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7C2BEC791C5195EE00B8EE90 /* String+SHA1.swift in Sources */,
-				7C73C6AA1C261A2100AEF6CA /* DemoServer.swift in Sources */,
-				7C73C6AB1C261A2100AEF6CA /* HttpHandlers.swift in Sources */,
-				7C1A2BFB1C5605F50026D3BF /* String+BASE64.swift in Sources */,
-				7C73C6AC1C261A2100AEF6CA /* HttpParser.swift in Sources */,
-				7C73C6AD1C261A2100AEF6CA /* HttpRequest.swift in Sources */,
-				7C73C6AE1C261A2100AEF6CA /* HttpResponse.swift in Sources */,
-				7C73C6AF1C261A2100AEF6CA /* HttpRouter.swift in Sources */,
-				7CC0F8CC1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */,
-				7C73C6B01C261A2100AEF6CA /* HttpServer.swift in Sources */,
-				7CCD87871C676EE50068099B /* File.swift in Sources */,
-				7C73C6B11C261A2100AEF6CA /* HttpServerIO.swift in Sources */,
-				7C73C6B21C261A2100AEF6CA /* Socket.swift in Sources */,
-				7CC0F8C91C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */,
-				7C73C6B31C261A2100AEF6CA /* String+Misc.swift in Sources */,
+				7C377E191D964B9F009C6148 /* String+File.swift in Sources */,
+				7C76B7171D2C45780030FC98 /* HttpResponse.swift in Sources */,
+				7C76B7211D2C45870030FC98 /* Scopes.swift in Sources */,
+				7C76B71F1D2C45840030FC98 /* Process.swift in Sources */,
+				7C76B7131D2C45730030FC98 /* HttpParser.swift in Sources */,
+				7C76B7271D2C458F0030FC98 /* String+Misc.swift in Sources */,
+				7C76B72B1D2C45940030FC98 /* WebSockets.swift in Sources */,
+				7C76B71B1D2C457E0030FC98 /* HttpServer.swift in Sources */,
+				7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */,
+				7C76B70D1D2C456A0030FC98 /* DemoServer.swift in Sources */,
+				7C76B29F1D369BEC00D35BFB /* Socket+File.swift in Sources */,
+				7C76B7231D2C45890030FC98 /* Socket.swift in Sources */,
+				7C76B71D1D2C45820030FC98 /* HttpServerIO.swift in Sources */,
+				7C76B7111D2C45710030FC98 /* Files.swift in Sources */,
+				7C76B7191D2C457C0030FC98 /* HttpRouter.swift in Sources */,
+				7C76B7291D2C45920030FC98 /* String+SHA1.swift in Sources */,
+				7C458EFC1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */,
+				7C76B7251D2C458C0030FC98 /* String+BASE64.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -590,21 +708,25 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7C2BEC7A1C5195F200B8EE90 /* String+SHA1.swift in Sources */,
-				7C73C6B51C261A2600AEF6CA /* DemoServer.swift in Sources */,
-				7C73C6B61C261A2600AEF6CA /* HttpHandlers.swift in Sources */,
-				7C1A2BFC1C5605F50026D3BF /* String+BASE64.swift in Sources */,
-				7C73C6B71C261A2600AEF6CA /* HttpParser.swift in Sources */,
-				7C73C6B81C261A2600AEF6CA /* HttpRequest.swift in Sources */,
-				7C73C6B91C261A2600AEF6CA /* HttpResponse.swift in Sources */,
-				7C73C6BA1C261A2600AEF6CA /* HttpRouter.swift in Sources */,
-				7CC0F8CD1C5014A200B65A94 /* HttpHandlers+WebSockets.swift in Sources */,
-				7C73C6BB1C261A2600AEF6CA /* HttpServer.swift in Sources */,
-				7CCD87881C676EE50068099B /* File.swift in Sources */,
-				7C73C6BC1C261A2600AEF6CA /* HttpServerIO.swift in Sources */,
-				7C73C6BD1C261A2600AEF6CA /* Socket.swift in Sources */,
-				7CC0F8CA1C50136B00B65A94 /* HttpHandlers+Files.swift in Sources */,
-				7C73C6BE1C261A2600AEF6CA /* String+Misc.swift in Sources */,
+				7C377E181D964B96009C6148 /* String+File.swift in Sources */,
+				7C76B7181D2C45790030FC98 /* HttpResponse.swift in Sources */,
+				7C76B7221D2C45870030FC98 /* Scopes.swift in Sources */,
+				7C76B7201D2C45840030FC98 /* Process.swift in Sources */,
+				7C76B7141D2C45730030FC98 /* HttpParser.swift in Sources */,
+				7C76B7281D2C458F0030FC98 /* String+Misc.swift in Sources */,
+				7C76B72C1D2C45950030FC98 /* WebSockets.swift in Sources */,
+				7C76B71C1D2C457E0030FC98 /* HttpServer.swift in Sources */,
+				7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */,
+				7C76B70E1D2C456B0030FC98 /* DemoServer.swift in Sources */,
+				7C76B2A01D369BEC00D35BFB /* Socket+File.swift in Sources */,
+				7C76B7241D2C458A0030FC98 /* Socket.swift in Sources */,
+				7C76B71E1D2C45820030FC98 /* HttpServerIO.swift in Sources */,
+				7C76B7121D2C45710030FC98 /* Files.swift in Sources */,
+				7C76B71A1D2C457C0030FC98 /* HttpRouter.swift in Sources */,
+				7C76B72A1D2C45920030FC98 /* String+SHA1.swift in Sources */,
+				7C458EFD1D4A7526006A68E5 /* Socket+Server.swift in Sources */,
+				7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */,
+				7C76B7261D2C458D0030FC98 /* String+BASE64.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -614,7 +736,6 @@
 			files = (
 				7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */,
 				7CDAB8161BE2A1D400C8A977 /* ViewController.swift in Sources */,
-				7C2BEC781C518B7C00B8EE90 /* String+SHA1.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -622,7 +743,6 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				7C73C6911C2615FE00AEF6CA /* SwiftyJSON.swift in Sources */,
 				7CA4813E19A2EA8D0030B30D /* main.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -632,6 +752,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 */,
 			);
@@ -641,7 +763,28 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7CCB8C621D97B8E9008B9712 /* SwifterTestsHttpRouter.swift in Sources */,
+				7CCB8C5E1D97B852008B9712 /* String+File.swift in Sources */,
+				7CEBB86F1D94612D00370A6B /* Files.swift in Sources */,
+				7CEBB8701D94612D00370A6B /* HttpParser.swift in Sources */,
+				7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */,
+				7CEBB8721D94612D00370A6B /* HttpResponse.swift in Sources */,
+				7CEBB8731D94612D00370A6B /* HttpRouter.swift in Sources */,
+				7CEBB8741D94612D00370A6B /* HttpServer.swift in Sources */,
+				7CEBB8751D94612D00370A6B /* HttpServerIO.swift in Sources */,
+				7CEBB8761D94612D00370A6B /* Process.swift in Sources */,
+				7CEBB8771D94612D00370A6B /* Scopes.swift in Sources */,
+				7CEBB8781D94612D00370A6B /* Socket.swift in Sources */,
+				7CEBB8791D94612D00370A6B /* Socket+File.swift in Sources */,
+				7CEBB87A1D94612D00370A6B /* Socket+Server.swift in Sources */,
+				7CEBB87B1D94612D00370A6B /* String+BASE64.swift in Sources */,
+				7CEBB87C1D94612D00370A6B /* String+Misc.swift in Sources */,
+				7CEBB87D1D94612D00370A6B /* String+SHA1.swift in Sources */,
+				7CEBB87E1D94612D00370A6B /* WebSockets.swift in Sources */,
+				7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */,
 				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 */,
 			);
@@ -665,11 +808,6 @@
 			target = 7AE893E61C05127900A29F63 /* SwifteriOS */;
 			targetProxy = 7CCD87621C66099B0068099B /* PBXContainerItemProxy */;
 		};
-		7CCD87801C660EA30068099B /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			target = 7AE893FA1C0512C400A29F63 /* SwifterMac */;
-			targetProxy = 7CCD877F1C660EA30068099B /* PBXContainerItemProxy */;
-		};
 /* End PBXTargetDependency section */
 
 /* Begin PBXVariantGroup section */
@@ -685,17 +823,73 @@
 /* End PBXVariantGroup section */
 
 /* Begin XCBuildConfiguration section */
+		269B479F1D3AAAE20042D137 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CURRENT_PROJECT_VERSION = 1.3.3;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				GCC_NO_COMMON_BLOCKS = YES;
+				INFOPLIST_FILE = SwiftertvOS/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_ENABLE_DEBUG_INFO = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = pl.kolakowski.SwiftertvOS;
+				PRODUCT_MODULE_NAME = Swifter;
+				PRODUCT_NAME = Swifter;
+				SDKROOT = appletvos;
+				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
+				TARGETED_DEVICE_FAMILY = 3;
+				TVOS_DEPLOYMENT_TARGET = 9.0;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		269B47A01D3AAAE20042D137 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1.3.3;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				GCC_NO_COMMON_BLOCKS = YES;
+				INFOPLIST_FILE = SwiftertvOS/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_ENABLE_DEBUG_INFO = NO;
+				PRODUCT_BUNDLE_IDENTIFIER = pl.kolakowski.SwiftertvOS;
+				PRODUCT_MODULE_NAME = Swifter;
+				PRODUCT_NAME = Swifter;
+				SDKROOT = appletvos;
+				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
+				TARGETED_DEVICE_FAMILY = 3;
+				TVOS_DEPLOYMENT_TARGET = 9.0;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
 		7AE893ED1C05127900A29F63 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 1.1.3;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEFINES_MODULE = YES;
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				EMBEDDED_CONTENT_CONTAINS_SWIFT = NO;
 				GCC_NO_COMMON_BLOCKS = YES;
-				INFOPLIST_FILE = SwifterSampleiOS/Info.plist;
+				INFOPLIST_FILE = SwifteriOS/Info.plist;
 				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
 				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -704,6 +898,7 @@
 				PRODUCT_NAME = Swifter;
 				SDKROOT = iphoneos;
 				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
 				VERSIONING_SYSTEM = "apple-generic";
 				VERSION_INFO_PREFIX = "";
 			};
@@ -713,14 +908,15 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1.1.3;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEFINES_MODULE = YES;
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				EMBEDDED_CONTENT_CONTAINS_SWIFT = NO;
 				GCC_NO_COMMON_BLOCKS = YES;
-				INFOPLIST_FILE = SwifterSampleiOS/Info.plist;
+				INFOPLIST_FILE = SwifteriOS/Info.plist;
 				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
 				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -729,6 +925,7 @@
 				PRODUCT_NAME = Swifter;
 				SDKROOT = iphoneos;
 				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
 				VERSIONING_SYSTEM = "apple-generic";
 				VERSION_INFO_PREFIX = "";
 			};
@@ -739,7 +936,7 @@
 			buildSettings = {
 				CODE_SIGN_IDENTITY = "Mac Developer";
 				COMBINE_HIDPI_IMAGES = YES;
-				CURRENT_PROJECT_VERSION = 1.1.3;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEFINES_MODULE = YES;
 				DYLIB_COMPATIBILITY_VERSION = 1;
@@ -756,6 +953,7 @@
 				PRODUCT_NAME = Swifter;
 				SDKROOT = macosx;
 				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
 				VERSIONING_SYSTEM = "apple-generic";
 				VERSION_INFO_PREFIX = "";
 			};
@@ -767,7 +965,7 @@
 				CODE_SIGN_IDENTITY = "Mac Developer";
 				COMBINE_HIDPI_IMAGES = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1.1.3;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEFINES_MODULE = YES;
 				DYLIB_COMPATIBILITY_VERSION = 1;
@@ -784,6 +982,7 @@
 				PRODUCT_NAME = Swifter;
 				SDKROOT = macosx;
 				SKIP_INSTALL = YES;
+				SWIFT_VERSION = 3.0;
 				VERSIONING_SYSTEM = "apple-generic";
 				VERSION_INFO_PREFIX = "";
 			};
@@ -793,6 +992,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				APPLICATION_EXTENSION_API_ONLY = YES;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -808,7 +1008,7 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1.1.4;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -832,6 +1032,7 @@
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = macosx;
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 3.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
 			name = Debug;
@@ -840,6 +1041,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				APPLICATION_EXTENSION_API_ONLY = YES;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -855,7 +1057,7 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
 				COPY_PHASE_STRIP = YES;
-				CURRENT_PROJECT_VERSION = 1.1.4;
+				CURRENT_PROJECT_VERSION = 1.3.3;
 				EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -871,6 +1073,7 @@
 				METAL_ENABLE_DEBUG_INFO = NO;
 				ONLY_ACTIVE_ARCH = NO;
 				SDKROOT = macosx;
+				SWIFT_VERSION = 3.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
 			};
@@ -952,6 +1155,7 @@
 				MTL_ENABLE_DEBUG_INFO = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = pl.kolakowski.SwifteriOSTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = iphoneos;
 			};
 			name = Debug;
 		};
@@ -968,6 +1172,7 @@
 				MTL_ENABLE_DEBUG_INFO = NO;
 				PRODUCT_BUNDLE_IDENTIFIER = pl.kolakowski.SwifteriOSTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = iphoneos;
 			};
 			name = Release;
 		};
@@ -1009,6 +1214,15 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
+		269B479E1D3AAAE20042D137 /* Build configuration list for PBXNativeTarget "SwiftertvOS" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				269B479F1D3AAAE20042D137 /* Debug */,
+				269B47A01D3AAAE20042D137 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		7AE893EC1C05127900A29F63 /* Build configuration list for PBXNativeTarget "SwifteriOS" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 0 - 0
Swifter.xcodeproj/project.xcworkspace/contents.xcworkspacedata → XCode/Swifter.xcodeproj/project.xcworkspace/contents.xcworkspacedata


+ 1 - 1
Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme → XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0730"
+   LastUpgradeVersion = "0800"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 1 - 1
Swifter.xcodeproj/xcshareddata/xcschemes/SwifteriOS.xcscheme → XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwifteriOS.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0730"
+   LastUpgradeVersion = "0800"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 80 - 0
XCode/Swifter.xcodeproj/xcshareddata/xcschemes/SwiftertvOS.xcscheme

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0800"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "269B47861D3AAAE20042D137"
+               BuildableName = "Swifter.framework"
+               BlueprintName = "SwiftertvOS"
+               ReferencedContainer = "container:Swifter.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "269B47861D3AAAE20042D137"
+            BuildableName = "Swifter.framework"
+            BlueprintName = "SwiftertvOS"
+            ReferencedContainer = "container:Swifter.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "269B47861D3AAAE20042D137"
+            BuildableName = "Swifter.framework"
+            BlueprintName = "SwiftertvOS"
+            ReferencedContainer = "container:Swifter.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 1 - 1
SwifterMac/Info.plist → XCode/SwifterMac/Info.plist

@@ -15,7 +15,7 @@
 	<key>CFBundlePackageType</key>
 	<string>FMWK</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.1.4</string>
+	<string>1.3.3</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>

+ 0 - 0
SwifterMac/SwifterMac.h → XCode/SwifterMac/SwifterMac.h


+ 0 - 0
SwifterOSXTests/Info.plist → XCode/SwifterOSXTests/Info.plist


+ 28 - 0
XCode/SwifterSampleOSX/main.swift

@@ -0,0 +1,28 @@
+//
+//  main.swift
+//  SwifterOSX
+//  Copyright (c) 2015 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+import Swifter
+
+do {
+    let server = demoServer(try String.File.currentWorkingDirectory())
+    server["/testAfterBaseRoute"] = { request in
+        return .ok(.html("ok !"))
+    }
+    
+    if #available(OSXApplicationExtension 10.10, *) {
+        try server.start(9080, forceIPv4: true)
+    } else {
+        // Fallback on earlier versions
+    }
+    
+    print("Server has started ( port = \(try server.port()) ). Try to connect now...")
+    
+    RunLoop.main.run()
+    
+} catch {
+    print("Server start error: \(error)")
+}

+ 2 - 3
SwifterSampleiOS/AppDelegate.swift → XCode/SwifterSampleiOS/AppDelegate.swift

@@ -12,8 +12,7 @@ import Swifter
 class AppDelegate: UIResponder, UIApplicationDelegate {
     
     var window: UIWindow?
-    
-    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
         return true
     }
-}
+}

+ 0 - 0
SwifterSampleiOS/Base.lproj/Main.storyboard → XCode/SwifterSampleiOS/Base.lproj/Main.storyboard


+ 0 - 0
SwifterSampleiOS/Images.xcassets/AppIcon.appiconset/Contents.json → XCode/SwifterSampleiOS/Images.xcassets/AppIcon.appiconset/Contents.json


+ 0 - 0
SwifterSampleiOS/Images.xcassets/LaunchImage.launchimage/Contents.json → XCode/SwifterSampleiOS/Images.xcassets/LaunchImage.launchimage/Contents.json


+ 1 - 1
SwifterSampleiOS/Info.plist → XCode/SwifterSampleiOS/Info.plist

@@ -15,7 +15,7 @@
 	<key>CFBundlePackageType</key>
 	<string>FMWK</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.1.4</string>
+	<string>1.3.3</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>

+ 0 - 0
SwifterSampleiOS/Launch Screen.storyboard → XCode/SwifterSampleiOS/Launch Screen.storyboard


+ 1 - 1
SwifterSampleiOS/ViewController.swift → XCode/SwifterSampleiOS/ViewController.swift

@@ -14,7 +14,7 @@ class ViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
         do {
-            let server = demoServer(NSBundle.mainBundle().resourcePath!)
+            let server = demoServer(Bundle.main.resourcePath!)
             try server.start(9080)
             self.server = server
         } catch {

+ 43 - 0
XCode/SwifterTestsCommon/IOSafetyTests.swift

@@ -0,0 +1,43 @@
+//
+//  IOSafetyTests.swift
+//  Swifter
+//
+//  Created by Brian Gerstle on 8/20/16.
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import XCTest
+
+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 { cpt in
+            server = HttpServer.pingServer()
+            do {
+                try server.start()
+                XCTAssertFalse(URLSession.shared.retryPing())
+                (0...100).forEach { _ in
+                    DispatchQueue.global(qos: DispatchQoS.default.qosClass).sync {
+                        URLSession.shared.pingTask { _, _, _ in }.resume()
+                    }
+                }
+                server.stop()
+            } catch let e {
+                XCTFail("\(cpt): \(e)")
+            }
+        }
+    }
+}

+ 63 - 0
XCode/SwifterTestsCommon/PingServer.swift

@@ -0,0 +1,63 @@
+//
+//  PingServer.swift
+//  Swifter
+//
+//  Created by Brian Gerstle on 8/20/16.
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+// Server
+extension HttpServer {
+    class func pingServer() -> HttpServer {
+        let server = HttpServer()
+        server.GET["/ping"] = { request in
+            return HttpResponse.ok(.text("pong!"))
+        }
+        return server
+    }
+}
+
+let defaultLocalhost = URL(string:"http://localhost:8080")!
+
+// Client
+extension URLSession {
+    func pingTask(
+        hostURL: URL = defaultLocalhost,
+        completionHandler handler: @escaping (Data?, URLResponse?, Error?) -> Void
+    ) -> URLSessionDataTask {
+        return self.dataTask(with: hostURL.appendingPathComponent("/ping"), completionHandler: handler)
+    }
+    
+    func retryPing(
+        hostURL: URL = defaultLocalhost,
+        timeout: Double = 2.0
+    ) -> Bool {
+        let semaphore = DispatchSemaphore(value: 0)
+        self.signalIfPongReceived(semaphore, hostURL: hostURL)
+        let timeoutDate = NSDate().addingTimeInterval(timeout)
+        var timedOut = false
+        while semaphore.wait(timeout: DispatchTime.now()) != DispatchTimeoutResult.timedOut {
+            if NSDate().laterDate(timeoutDate as Date) != timeoutDate as Date {
+                timedOut = true
+                break
+            }
+            RunLoop.current.run(
+                mode: RunLoopMode.commonModes,
+                before: NSDate.distantFuture
+            )
+        }
+        return timedOut
+    }
+    
+    func signalIfPongReceived(_ semaphore: DispatchSemaphore, hostURL: URL) {
+        pingTask(hostURL: hostURL) { data, response, error in
+            if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
+                semaphore.signal()
+            } else {
+                self.signalIfPongReceived(semaphore, hostURL: hostURL)
+            }
+        }.resume()
+    }
+}

+ 28 - 27
SwifterTestsCommon/SwifterTestsHttpParser.swift → XCode/SwifterTestsCommon/SwifterTestsHttpParser.swift

@@ -6,7 +6,6 @@
 //
 
 import XCTest
-import Swifter
 
 class SwifterTestsHttpParser: XCTestCase {
     
@@ -16,16 +15,16 @@ class SwifterTestsHttpParser: XCTestCase {
         
         init(_ content: String) {
             super.init(socketFileDescriptor: -1)
-            self.content.appendContentsOf([UInt8](content.utf8))
+            self.content.append(contentsOf: [UInt8](content.utf8))
         }
-
+        
         override func read() throws -> UInt8 {
             if offset < content.count {
                 let value = self.content[offset]
                 offset = offset + 1
                 return value
             }
-            throw SocketError.RecvFailed("")
+            throw SocketError.recvFailed("")
         }
     }
     
@@ -33,73 +32,75 @@ class SwifterTestsHttpParser: XCTestCase {
         let parser = HttpParser()
         
         do {
-            try parser.readHttpRequest(TestSocket(""))
+            let _ = try parser.readHttpRequest(TestSocket(""))
             XCTAssert(false, "Parser should throw an error if socket is empty.")
         } catch { }
-
+        
         do {
-            try parser.readHttpRequest(TestSocket("12345678"))
+            let _ = try parser.readHttpRequest(TestSocket("12345678"))
             XCTAssert(false, "Parser should throw an error if status line has single token.")
         } catch { }
-
+        
         do {
-            try parser.readHttpRequest(TestSocket("GET HTTP/1.0"))
+            let _ = try parser.readHttpRequest(TestSocket("GET HTTP/1.0"))
             XCTAssert(false, "Parser should throw an error if status line has not enough tokens.")
         } catch { }
-
+        
         do {
-            try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
             XCTAssert(false, "Parser should throw an error if there is no next line symbol.")
         } catch { }
-            
+        
         do {
-            try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0"))
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r"))
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\n"))
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"))
         } catch {
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 0\r\n\n"))
         } catch {
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 5\n\n12345"))
         } catch {
             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"))
+            let _ = try parser.readHttpRequest(TestSocket("GET / HTTP/1.0\nContent-Length: 10\r\n\n"))
             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"))
-        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.")
+        var r = try? parser.readHttpRequest(TestSocket("GET /open?link=https://www.youtube.com/watch?v=D2cUBG4PnOA HTTP/1.0\nContent-Length: 10\n\n1234567890"))
+        
+        XCTAssertEqual(r?.queryParams.filter({ $0.0 == "link"}).first?.1, "https://www.youtube.com/watch?v=D2cUBG4PnOA")
+        XCTAssertEqual(r?.method, "GET", "Parser should extract HTTP method name from the status line.")
+        XCTAssertEqual(r?.path, "/open?link=https://www.youtube.com/watch?v=D2cUBG4PnOA", "Parser should extract HTTP path value from the status line.")
+        XCTAssertEqual(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"))
-        XCTAssert(r?.method == "POST", "Parser should extract HTTP method name from the status line.")
+        XCTAssertEqual(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"))
-        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.")
+        XCTAssertEqual(r?.headers["header1"], "1", "Parser should extract multiple headers from the request.")
+        XCTAssertEqual(r?.headers["header2"], "2", "Parser should extract multiple headers from the request.")
     }
-}
+}

+ 108 - 0
XCode/SwifterTestsCommon/SwifterTestsHttpRouter.swift

@@ -0,0 +1,108 @@
+//
+//  SwifterTestsHttpRouter.swift
+//  Swifter
+//
+
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import XCTest
+
+class SwifterTestsHttpRouter: XCTestCase {
+    
+    func testHttpRouterSlashRoot() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        XCTAssertNotNil(router.route(nil, path: "/"))
+    }
+    
+    func testHttpRouterSimplePathSegments() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/a/b/c/d", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        XCTAssertNil(router.route(nil, path: "/"))
+        XCTAssertNil(router.route(nil, path: "/a"))
+        XCTAssertNil(router.route(nil, path: "/a/b"))
+        XCTAssertNil(router.route(nil, path: "/a/b/c"))
+        XCTAssertNotNil(router.route(nil, path: "/a/b/c/d"))
+    }
+    
+    func testHttpRouterSinglePathSegmentWildcard() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/a/*/c/d", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        XCTAssertNil(router.route(nil, path: "/"))
+        XCTAssertNil(router.route(nil, path: "/a"))
+        XCTAssertNotNil(router.route(nil, path: "/a/foo/c/d"))
+        XCTAssertNotNil(router.route(nil, path: "/a/b/c/d"))
+        XCTAssertNil(router.route(nil, path: "/a/b"))
+        XCTAssertNil(router.route(nil, path: "/a/b/foo/d"))
+    }
+    
+    func testHttpRouterVariables() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/a/:arg1/:arg2/b/c/d/:arg3", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        XCTAssertNil(router.route(nil, path: "/"))
+        XCTAssertNil(router.route(nil, path: "/a"))
+        XCTAssertNil(router.route(nil, path: "/a/b/c/d"))
+        XCTAssertEqual(router.route(nil, path: "/a/value1/value2/b/c/d/value3")?.0[":arg1"], "value1")
+        XCTAssertEqual(router.route(nil, path: "/a/value1/value2/b/c/d/value3")?.0[":arg2"], "value2")
+        XCTAssertEqual(router.route(nil, path: "/a/value1/value2/b/c/d/value3")?.0[":arg3"], "value3")
+    }
+    
+    func testHttpRouterMultiplePathSegmentWildcards() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/a/**/e/f/g", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        XCTAssertNil(router.route(nil, path: "/"))
+        XCTAssertNil(router.route(nil, path: "/a"))
+        XCTAssertNotNil(router.route(nil, path: "/a/b/c/d/e/f/g"))
+        XCTAssertNil(router.route(nil, path: "/a/e/f/g"))
+    }
+    
+    func testHttpRouterEmptyTail() {
+        
+        let router = HttpRouter()
+        
+        router.register(nil, path: "/a/b/", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        router.register(nil, path: "/a/b/:var", handler: { r in
+            return .ok(.html("OK"))
+        })
+        
+        
+        XCTAssertNil(router.route(nil, path: "/"))
+        XCTAssertNil(router.route(nil, path: "/a"))
+        XCTAssertNotNil(router.route(nil, path: "/a/b/"))
+        XCTAssertNil(router.route(nil, path: "/a/e/f/g"))
+        
+        XCTAssertEqual(router.route(nil, path: "/a/b/value1")?.0[":var"], "value1")
+        
+        XCTAssertEqual(router.route(nil, path: "/a/b/")?.0[":var"], "")
+    }
+    
+}

+ 34 - 38
SwifterTestsCommon/SwifterTestsStringExtensions.swift → XCode/SwifterTestsCommon/SwifterTestsStringExtensions.swift

@@ -6,19 +6,18 @@
 //
 
 import XCTest
-import Swifter
 
 class SwifterTestsStringExtensions: XCTestCase {
     
     func testSHA1() {
-        XCTAssertEqual("".SHA1(), "da39a3ee5e6b4b0d3255bfef95601890afd80709")
-        XCTAssertEqual("test".SHA1(), "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3")
+        XCTAssertEqual("".sha1(), "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+        XCTAssertEqual("test".sha1(), "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3")
         
         // Values copied from OpenSSL:
         // https://github.com/openssl/openssl/blob/master/test/sha1test.c
         
-        XCTAssertEqual("abc".SHA1(), "a9993e364706816aba3e25717850c26c9cd0d89d")
-        XCTAssertEqual("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".SHA1(),
+        XCTAssertEqual("abc".sha1(), "a9993e364706816aba3e25717850c26c9cd0d89d")
+        XCTAssertEqual("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".sha1(),
             "84983e441c3bd26ebaae4aa1f95129e5e54670f1")
         
         XCTAssertEqual(
@@ -34,7 +33,7 @@ class SwifterTestsStringExtensions: XCTestCase {
              "a9993e364706816aba3e25717850c26c9cd0d89d" +
              "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +
              "a9993e364706816aba3e25717850c26c9cd0d89d" +
-             "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq").SHA1(),
+             "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq").sha1(),
             "a377b0c42d685fdc396e29a9eda7101d900947ca")
     }
     
@@ -69,43 +68,40 @@ class SwifterTestsStringExtensions: XCTestCase {
     }
     
     func testMiscTrim() {
-        XCTAssertEqual("".trim(), "")
-        XCTAssertEqual("\n".trim(), "")
-        XCTAssertEqual("\t".trim(), "")
-        XCTAssertEqual("\r".trim(), "")
-        XCTAssertEqual(" ".trim(), "")
-        XCTAssertEqual("      ".trim(), "")
-        XCTAssertEqual("1 test     ".trim(), "1 test")
-        XCTAssertEqual("      test          ".trim(), "test")
-        XCTAssertEqual("   \t\n\rtest          ".trim(), "test")
-        XCTAssertEqual("   \t\n\rtest  n   \n\t asd    ".trim(), "test  n   \n\t asd")
+        XCTAssertEqual("".trimmingCharacters(in: .whitespacesAndNewlines), "")
+        XCTAssertEqual(" ".trimmingCharacters(in: .whitespacesAndNewlines), "")
+        XCTAssertEqual("      ".trimmingCharacters(in: .whitespacesAndNewlines), "")
+        XCTAssertEqual("1 test     ".trimmingCharacters(in: .whitespacesAndNewlines), "1 test")
+        XCTAssertEqual("      test          ".trimmingCharacters(in: .whitespacesAndNewlines), "test")
+        XCTAssertEqual("   \t\n\rtest          ".trimmingCharacters(in: .whitespacesAndNewlines), "test")
+        XCTAssertEqual("   \t\n\rtest  n   \n\t asd    ".trimmingCharacters(in: .whitespacesAndNewlines), "test  n   \n\t asd")
     }
 
     func testMiscReplace() {
-        XCTAssertEqual("".replace("+", "-"), "")
-        XCTAssertEqual("test".replace("+", "-"), "test")
-        XCTAssertEqual("+++".replace("+", "-"), "---")
-        XCTAssertEqual("t&e&s&t12%3%".replace("&", "+").replace("%", "+"), "t+e+s+t12+3+")
-        XCTAssertEqual("test 1234 #$%^&*( test   ".replace(" ", "_"), "test_1234_#$%^&*(_test___")
+        XCTAssertEqual("".replacingOccurrences(of: "+", with: "-"), "")
+        XCTAssertEqual("test".replacingOccurrences(of: "+", with: "-"), "test")
+        XCTAssertEqual("+++".replacingOccurrences(of: "+", with: "-"), "---")
+        XCTAssertEqual("t&e&s&t12%3%".replacingOccurrences(of: "&", with: "+").replacingOccurrences(of: "%", with: "+"), "t+e+s+t12+3+")
+        XCTAssertEqual("test 1234 #$%^&*( test   ".replacingOccurrences(of: " ", with: "_"), "test_1234_#$%^&*(_test___")
     }
     
     func testMiscRemovePercentEncoding() {
-        XCTAssertEqual("".removePercentEncoding(), "")
-        XCTAssertEqual("%20".removePercentEncoding(), " ")
-        XCTAssertEqual("%22".removePercentEncoding(), "\"")
-        XCTAssertEqual("%25".removePercentEncoding(), "%")
-        XCTAssertEqual("%2d".removePercentEncoding(), "-")
-        XCTAssertEqual("%2e".removePercentEncoding(), ".")
-        XCTAssertEqual("%3C".removePercentEncoding(), "<")
-        XCTAssertEqual("%3E".removePercentEncoding(), ">")
-        XCTAssertEqual("%5C".removePercentEncoding(), "\\")
-        XCTAssertEqual("%5E".removePercentEncoding(), "^")
-        XCTAssertEqual("%5F".removePercentEncoding(), "_")
-        XCTAssertEqual("%60".removePercentEncoding(), "`")
-        XCTAssertEqual("%7B".removePercentEncoding(), "{")
-        XCTAssertEqual("%7C".removePercentEncoding(), "|")
-        XCTAssertEqual("%7D".removePercentEncoding(), "}")
-        XCTAssertEqual("%7E".removePercentEncoding(), "~")
-        XCTAssertEqual("%7e".removePercentEncoding(), "~")
+        XCTAssertEqual("".removingPercentEncoding!, "")
+        XCTAssertEqual("%20".removingPercentEncoding!, " ")
+        XCTAssertEqual("%22".removingPercentEncoding!, "\"")
+        XCTAssertEqual("%25".removingPercentEncoding!, "%")
+        XCTAssertEqual("%2d".removingPercentEncoding!, "-")
+        XCTAssertEqual("%2e".removingPercentEncoding!, ".")
+        XCTAssertEqual("%3C".removingPercentEncoding!, "<")
+        XCTAssertEqual("%3E".removingPercentEncoding!, ">")
+        XCTAssertEqual("%5C".removingPercentEncoding!, "\\")
+        XCTAssertEqual("%5E".removingPercentEncoding!, "^")
+        XCTAssertEqual("%5F".removingPercentEncoding!, "_")
+        XCTAssertEqual("%60".removingPercentEncoding!, "`")
+        XCTAssertEqual("%7B".removingPercentEncoding!, "{")
+        XCTAssertEqual("%7C".removingPercentEncoding!, "|")
+        XCTAssertEqual("%7D".removingPercentEncoding!, "}")
+        XCTAssertEqual("%7E".removingPercentEncoding!, "~")
+        XCTAssertEqual("%7e".removingPercentEncoding!, "~")
     }
 }

+ 36 - 28
SwifterTestsCommon/SwifterTestsWebSocketSession.swift → XCode/SwifterTestsCommon/SwifterTestsWebSocketSession.swift

@@ -6,7 +6,6 @@
 //
 
 import XCTest
-import Swifter
 
 class SwifterTestsWebSocketSession: XCTestCase {
     
@@ -16,7 +15,7 @@ class SwifterTestsWebSocketSession: XCTestCase {
         
         init(_ content: [UInt8]) {
             super.init(socketFileDescriptor: -1)
-            self.content.appendContentsOf(content)
+            self.content.append(contentsOf: content)
         }
         
         override func read() throws -> UInt8 {
@@ -25,83 +24,92 @@ class SwifterTestsWebSocketSession: XCTestCase {
                 offset = offset + 1
                 return value
             }
-            throw SocketError.RecvFailed("")
+            throw SocketError.recvFailed("")
         }
     }
     
     func testParser() {
-        let session = HttpHandlers.WebSocketSession(TestSocket([]))
         
         do {
-            try session.readFrame(TestSocket([0]))
+            let session = WebSocketSession(TestSocket([0]))
+            let _ = try session.readFrame()
             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]))
+            let session = WebSocketSession(TestSocket([0b0000_0001, 0b0000_0000, 0, 0, 0, 0]))
+            let _ = try session.readFrame()
             XCTAssert(false, "Parser should not accept unmasked frames.")
-        } catch HttpHandlers.WebSocketSession.Error.UnMaskedFrame {
+        } catch WebSocketSession.WsError.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]))
+            let session = WebSocketSession(TestSocket([0b1000_0001, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
             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.")
+            let session = WebSocketSession(TestSocket([0b0000_0000, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, 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.")
+            let session = WebSocketSession(TestSocket([0b0000_0001, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, 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.")
+            let session = WebSocketSession(TestSocket([0b0000_0010, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, 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.")
+            let session = WebSocketSession(TestSocket([0b1000_1000, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, WebSocketSession.OpCode.close, "Parser should accept Close opcode.")
+        } catch let e {
+            XCTAssert(false, "Parser should accept Close opcode without any errors. \(e)")
         }
         
         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.")
+            let session = WebSocketSession(TestSocket([0b1000_1001, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, WebSocketSession.OpCode.ping, "Parser should accept Ping opcode.")
+        } catch let e {
+            XCTAssert(false, "Parser should accept Ping opcode without any errors. \(e)")
         }
         
         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.")
+            let session = WebSocketSession(TestSocket([0b1000_1010, 0b1000_0000, 0, 0, 0, 0]))
+            let frame = try session.readFrame()
+            XCTAssertEqual(frame.opcode, WebSocketSession.OpCode.pong, "Parser should accept Pong opcode.")
+        } catch let e {
+            XCTAssert(false, "Parser should accept Pong opcode without any errors. \(e)")
         }
         
         for 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]))
+                let session = WebSocketSession(TestSocket([UInt8(opcode), 0b1000_0000, 0, 0, 0, 0]))
+                let _ = try session.readFrame()
                 XCTAssert(false, "Parse should throw an error for unknown opcode: \(opcode)")
-            } catch HttpHandlers.WebSocketSession.Error.UnknownOpCode(_) {
+            } catch WebSocketSession.WsError.unknownOpCode(_) {
                 XCTAssert(true, "Parse should throw UnknownOpCode error for unknown opcode.")
             } catch {
                 XCTAssert(false, "Parse should throw UnknownOpCode error for unknown opcode (was \(error)).")

+ 28 - 0
XCode/SwifteriOS/Info.plist

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.3.3</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2015 Damian Kołakowski. All rights reserved.</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 0 - 0
SwifteriOS/SwifteriOS.h → XCode/SwifteriOS/SwifteriOS.h


+ 0 - 0
SwifteriOSTests/Info.plist → XCode/SwifteriOSTests/Info.plist


+ 28 - 0
XCode/SwiftertvOS/Info.plist

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.3.3</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2015 Damian Kołakowski. All rights reserved.</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 19 - 0
XCode/SwiftertvOS/SwiftertvOS.h

@@ -0,0 +1,19 @@
+//
+//  SwiftertvOS.h
+//  SwiftertvOS
+//
+//  Created by Romain Pouclet on 2015-11-24.
+//  Copyright © 2015 Damian Kołakowski. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+//! Project version number for SwiftertvOS.
+FOUNDATION_EXPORT double SwiftertvOSVersionNumber;
+
+//! Project version string for SwifteriOS.
+FOUNDATION_EXPORT const unsigned char SwiftertvOSVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <SwifteriOS/PublicHeader.h>
+
+