Răsfoiți Sursa

Naming “TcpServer” -> “IO”.
Added multipart support for Request class (Http+Misc).
Added support for url encoded body to Request class (Http+Misc).

Damian Kołakowski 9 ani în urmă
părinte
comite
b97a19e7be

+ 0 - 15
Sources/Errno.swift

@@ -1,15 +0,0 @@
-//
-//  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)))
-    }
-}

+ 10 - 1
Sources/Error.swift

@@ -7,7 +7,8 @@
 
 import Foundation
 
-public enum AsyncError: Error {
+public enum SwifterError: Error {
+    
     case parse(String)
     case async(String)
     case socketCreation(String)
@@ -24,4 +25,12 @@ public enum AsyncError: Error {
     case acceptFailed(String)
     case readFailed(String)
     case httpError(String)
+    case inetPtonFailed(String)
+}
+
+public class Errno {
+    
+    public class func description() -> String {
+        return String(cString: UnsafePointer(strerror(errno)))
+    }
 }

+ 50 - 20
Sources/HttpRequest.swift → Sources/Http+Misc.swift

@@ -1,31 +1,31 @@
 //
-//  HttpRequest.swift
+//  HttpPost.swift
 //  Swifter
 //
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//  Created by Damian Kolakowski on 24/02/2017.
+//  Copyright © 2017 Damian Kołakowski. All rights reserved.
 //
 
 import Foundation
 
-public class HttpRequest {
-    
-    public var path: String = ""
-    public var queryParams: [(String, String)] = []
-    public var method: String = ""
-    public var headers: [String: String] = [:]
-    public var body: [UInt8] = []
-    public var address: String? = ""
-    public var params: [String: String] = [:]
+
+extension Request {
     
-    public func hasTokenForHeader(_ headerName: String, token: String) -> Bool {
-        guard let headerValue = headers[headerName] else {
+    public func hasToken(_ token: String, forHeader headerName: String) -> Bool {
+        guard let (_, value) = headers.filter({ $0.0 == headerName }).first else {
             return false
         }
-        return headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).count > 0
+        return value
+            .components(separatedBy: ",")
+            .filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token })
+            .count > 0
     }
+}
+
+extension Request {
     
     public func parseUrlencodedForm() -> [(String, String)] {
-        guard let contentTypeHeader = headers["content-type"] else {
+        guard let (_, contentTypeHeader) = headers.filter({ $0.0 == "content-type"}).last else {
             return []
         }
         let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
@@ -45,6 +45,36 @@ public class HttpRequest {
             return ("","")
         }
     }
+}
+
+extension String {
+    
+    public func unquote() -> String {
+        var scalars = self.unicodeScalars;
+        if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 {
+            scalars.removeFirst();
+            scalars.removeLast();
+            return String(scalars)
+        }
+        return self
+    }
+}
+
+extension UnicodeScalar {
+    
+    public func asWhitespace() -> UInt8? {
+        if self.value >= 9 && self.value <= 13 {
+            return UInt8(self.value)
+        }
+        if self.value == 32 {
+            return UInt8(self.value)
+        }
+        return nil
+    }
+    
+}
+
+extension Request {
     
     public struct MultiPart {
         
@@ -77,7 +107,7 @@ public class HttpRequest {
     }
     
     public func parseMultiPartFormData() -> [MultiPart] {
-        guard let contentTypeHeader = headers["content-type"] else {
+        guard let (_, contentTypeHeader) = headers.filter({ $0.0 == "content-type"}).last else {
             return []
         }
         let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
@@ -130,10 +160,10 @@ public class HttpRequest {
     private func nextUTF8MultiPartLine(_ generator: inout IndexingIterator<[UInt8]>) -> String? {
         var temp = [UInt8]()
         while let value = generator.next() {
-            if value > HttpRequest.CR {
+            if value > UInt8.cr {
                 temp.append(value)
             }
-            if value == HttpRequest.NL {
+            if value == UInt8.lf {
                 break
             }
         }
@@ -152,9 +182,9 @@ public class HttpRequest {
             body.append(x)
             if matchOffset == boundaryArray.count {
                 body.removeSubrange(CountableRange<Int>(body.count-matchOffset ..< body.count))
-                if body.last == HttpRequest.NL {
+                if body.last == UInt8.lf {
                     body.removeLast()
-                    if body.last == HttpRequest.CR {
+                    if body.last == UInt8.cr {
                         body.removeLast()
                     }
                 }

+ 8 - 18
Sources/Http.swift

@@ -24,16 +24,6 @@ open class Request {
     public var body = [UInt8]()
     
     public var contentLength = 0
-    
-    public func hasToken(_ token: String, forHeader headerName: String) -> Bool {
-        guard let (_, value) = headers.filter({ $0.0 == headerName }).first else {
-            return false
-        }
-        return value
-            .components(separatedBy: ",")
-            .filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token })
-            .count > 0
-    }
 }
 
 open class Response {
@@ -124,7 +114,7 @@ public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
         case .waitingForHeaders:
             
             guard self.buffer.count + chunk.count < 4096 else {
-                throw AsyncError.parse("Headers size exceeds the limit.")
+                throw SwifterError.parse("Headers size exceeds the limit.")
             }
             
             var iterator = chunk.makeIterator()
@@ -147,7 +137,7 @@ public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
         case .waitingForBody:
             
             guard self.request.body.count + chunk.count <= request.contentLength else {
-                throw AsyncError.parse("Peer sent more data then required ('Content-Length' = \(request.contentLength).")
+                throw SwifterError.parse("Peer sent more data then required ('Content-Length' = \(request.contentLength).")
             }
             
             request.body.append(contentsOf: chunk)
@@ -164,13 +154,13 @@ public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
         let lines = data.split(separator: UInt8.lf)
         
         guard let requestLine = lines.first else {
-            throw AsyncError.httpError("No status line.")
+            throw SwifterError.httpError("No status line.")
         }
         
         let requestLineTokens = requestLine.split(separator: UInt8.space)
         
         guard requestLineTokens.count >= 3 else {
-            throw AsyncError.httpError("Invalid status line.")
+            throw SwifterError.httpError("Invalid status line.")
         }
         
         let request = Request()
@@ -180,7 +170,7 @@ public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
         } else if requestLineTokens[2] == [0x48, 0x54,  0x54,  0x50, 0x2f, 0x31, 0x2e, 0x31] {
             request.httpVersion = .http11
         } else {
-            throw AsyncError.parse("Invalid http version: \(requestLineTokens[2])")
+            throw SwifterError.parse("Invalid http version: \(requestLineTokens[2])")
         }
         
         request.headers = lines
@@ -200,19 +190,19 @@ public class HttpIncomingDataPorcessor: Hashable, IncomingDataProcessor {
             .filter({ $0.0 == "content-length" })
             .first {
             guard let contentLength = Int(value) else {
-                throw AsyncError.parse("Invalid 'Content-Length' header value \(value).")
+                throw SwifterError.parse("Invalid 'Content-Length' header value \(value).")
             }
             request.contentLength = contentLength
         }
         
         guard let method = String(bytes: requestLineTokens[0], encoding: .ascii) else {
-            throw AsyncError.parse("Invalid 'method' value \(requestLineTokens[0]).")
+            throw SwifterError.parse("Invalid 'method' value \(requestLineTokens[0]).")
         }
         
         request.method = method
         
         guard let path = String(bytes: requestLineTokens[1], encoding: .ascii) else {
-            throw AsyncError.parse("Invalid 'path' value \(requestLineTokens[1]).")
+            throw SwifterError.parse("Invalid 'path' value \(requestLineTokens[1]).")
         }
         
         let queryComponents = path.components(separatedBy: "?")

+ 5 - 5
Sources/Tcp.swift → Sources/IO.swift

@@ -7,23 +7,23 @@
 
 import Foundation
 
-public protocol TcpServer: class {
+public protocol IO: class {
     
     init(_ port: in_port_t, forceIPv4: Bool, bindAddress: String?) throws
     
-    func wait(_ callback: ((TcpServerEvent) -> Void)) throws
+    func wait(_ callback: ((IOEvent) -> Void)) throws
     
-    func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws
+    func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> IODoneAction)) throws
     
     func finish(_ socket: Int32)
 }
 
-public enum TcpWriteDoneAction {
+public enum IODoneAction {
     
     case `continue`, terminate
 }
 
-public enum TcpServerEvent {
+public enum IOEvent {
     
     case connect(String, Int32)
     

+ 72 - 27
Sources/Linux.swift

@@ -9,15 +9,17 @@
     
 import Glibc
 
-public class LinuxAsyncServer: TcpServer {
+public class LinuxIO: IO {
+    
+    private var backlog = [Int32: Array<(chunk: [UInt8], done: ((Void) -> IODoneAction))>]()
     
-    private var backlog = [Int32: Array<(chunk: [UInt8], done: ((Void) -> TcpWriteDoneAction))>]()
     private var descriptors = [pollfd]()
+    
     private let server: Int32
     
     public required init(_ port: in_port_t, forceIPv4: Bool, bindAddress: String? = nil) throws {
         
-        self.server = try LinuxAsyncServer.nonBlockingSocketForListenening(port)
+        self.server = try LinuxAsyncServer.nonBlockingSocketForListenening(port, forceIPv4: forceIPv4, address: bindAddress)
         
         self.descriptors.append(pollfd(fd: self.server, events: Int16(POLLIN), revents: 0))
     }
@@ -26,7 +28,7 @@ public class LinuxAsyncServer: TcpServer {
         cleanup()
     }
     
-    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws {
+    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> IODoneAction)) throws {
         let result = Glibc.write(socket, data, data.count)
         if result == -1 {
             defer { self.finish(socket) }
@@ -48,7 +50,7 @@ public class LinuxAsyncServer: TcpServer {
         }
     }
     
-    public func wait(_ callback: ((TcpServerEvent) -> Void)) throws {
+    public func wait(_ callback: ((IOEvent) -> Void)) throws {
         guard poll(&descriptors, UInt(descriptors.count), -1) != -1 else {
             throw AsyncError.async(Process.error)
         }
@@ -61,13 +63,13 @@ public class LinuxAsyncServer: TcpServer {
                     try LinuxAsyncServer.setSocketNonBlocking(client)
                     self.backlog[Int32(client)] = []
                     descriptors.append(pollfd(fd: client, events: Int16(POLLIN), revents: 0))
-                    callback(TcpServerEvent.connect("", Int32(client)))
+                    callback(IOEvent.connect("", Int32(client)))
                 }
                 if errno != EWOULDBLOCK { throw AsyncError.acceptFailed(Process.error) }
             } else {
                 if (descriptors[i].revents & Int16(POLLERR) != 0) || (descriptors[i].revents & Int16(POLLHUP) != 0) || (descriptors[i].revents & Int16(POLLNVAL) != 0) {
                     self.finish(descriptors[i].fd)
-                    callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                    callback(IOEvent.disconnect("", descriptors[i].fd))
                     descriptors[i].fd = -1
                     continue
                 }
@@ -78,18 +80,18 @@ public class LinuxAsyncServer: TcpServer {
                         switch result {
                         case -1:
                             if errno != EWOULDBLOCK {
-                                callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                                callback(IOEvent.disconnect("", descriptors[i].fd))
                                 self.finish(descriptors[i].fd)
                                 descriptors[i].fd = -1
                             }
                             break readLoop
                         case 0:
-                            callback(TcpServerEvent.disconnect("", descriptors[i].fd))
+                            callback(IOEvent.disconnect("", descriptors[i].fd))
                             self.finish(descriptors[i].fd)
                             descriptors[i].fd = -1
                             break readLoop
                         default:
-                            callback(TcpServerEvent.data("", descriptors[i].fd, buffer[0..<result]))
+                            callback(IOEvent.data("", descriptors[i].fd, buffer[0..<result]))
                         }
                     }
                 }
@@ -99,7 +101,7 @@ public class LinuxAsyncServer: TcpServer {
                         let result = Glibc.write(Int32(descriptors[i].fd), chunk, chunk.count)
                         if result == -1 {
                             finish(Int32(descriptors[i].fd))
-                            callback(TcpServerEvent.disconnect("", Int32(descriptors[i].fd)))
+                            callback(IOEvent.disconnect("", Int32(descriptors[i].fd)))
                             descriptors[i].fd = -1
                             return
                         }
@@ -112,7 +114,7 @@ public class LinuxAsyncServer: TcpServer {
                         self.backlog[Int32(descriptors[i].fd)]?.removeFirst()
                         if backlogElement.done() == .terminate {
                             finish(Int32(descriptors[i].fd))
-                            callback(TcpServerEvent.disconnect("", Int32(descriptors[i].fd)))
+                            callback(IOEvent.disconnect("", Int32(descriptors[i].fd)))
                             descriptors[i].fd = -1
                             return
                         }
@@ -140,9 +142,9 @@ public class LinuxAsyncServer: TcpServer {
         let _ = Glibc.close(Int32(server))
     }
     
-    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080) throws -> Int32 {
+    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080, forceIPv4: Bool = false, address: String? = nil) throws -> Int32 {
         
-        let server = Glibc.socket(AF_INET, Int32(SOCK_STREAM.rawValue), 0)
+        let server = Glibc.socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0)
         
         guard server != -1 else {
             throw AsyncError.socketCreation(Process.error)
@@ -154,16 +156,16 @@ public class LinuxAsyncServer: TcpServer {
             throw AsyncError.setReuseAddrFailed(Process.error)
         }
         
-        if Glibc.fcntl(server, F_SETFL, fcntl(server, F_GETFL, 0) | O_NONBLOCK) == -1 {
-            defer { let _ = Glibc.close(server) }
-            throw AsyncError.setNonBlockFailed(Process.error)
-        }
-        
-        var addr = anyAddrForPort(port)
-        
-        if withUnsafePointer(to: &addr, { bind(server, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size)) })  == -1 {
-            defer { let _ = Glibc.close(server) }
-            throw AsyncError.bindFailed(Process.error)
+        do {
+            try setSocketNonBlocking(server)
+            if forceIPv4 {
+                try bind(toSocket: server, port: port, andIPv4Address: address)
+            } else {
+                try bind(toSocket: server, port: port, andAddress: address)
+            }
+        } catch {
+            let _ = Glibc.close(server)
+            throw error
         }
         
         if Glibc.listen(server, SOMAXCONN) == -1 {
@@ -174,15 +176,58 @@ public class LinuxAsyncServer: TcpServer {
         return server
     }
     
-    public static func anyAddrForPort(_ port: in_port_t) -> sockaddr_in {
+    public static func bind(toSocket socket: Int32, port: in_port_t, andIPv4Address address: String? = nil) throws  {
+        
         var addr = sockaddr_in()
+        
         addr.sin_family = sa_family_t(AF_INET)
         addr.sin_port = port.bigEndian
-        addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        
+        if let addressFound = address {
+            guard addressFound.withCString({ inet_pton(AF_INET, $0, &addr.sin_addr) }) == 1 else {
+                throw AsyncError.inetPtonFailed(Errno.description())
+            }
+        } else {
+            addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        }
+        
         addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
-        return addr
+        
+        let bindResult = withUnsafePointer(to: &addr) {
+            Glibc.bind(socket, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
+        }
+        
+        guard bindResult != -1 else {
+            throw AsyncError.bindFailed(Errno.description())
+        }
     }
     
+    public static func bind(toSocket socket: Int32, port: in_port_t, andAddress address: String? = nil) throws {
+        
+        var addr = sockaddr_in6()
+        
+        addr.sin6_family = sa_family_t(AF_INET6)
+        addr.sin6_port = port.bigEndian
+        
+        if let addressFound = address {
+            guard addressFound.withCString({ inet_pton(AF_INET6, $0, &addr.sin6_addr) }) == 1 else {
+                throw AsyncError.inetPtonFailed(Errno.description())
+            }
+        } else {
+            addr.sin6_addr = in6addr_any
+        }
+        
+        addr.sin6_scope_id = 0
+        
+        let bindResult = withUnsafePointer(to: &addr) {
+            Glibc.bind(socket, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in6>.size))
+        }
+        
+        guard bindResult != -1 else {
+            throw AsyncError.bindFailed(Errno.description())
+        }
+    }
+
     public static func setSocketNonBlocking(_ socket: Int32) throws {
         if Glibc.fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK) == -1 {
             throw AsyncError.setNonBlockFailed(Process.error)

+ 81 - 30
Sources/MacOS.swift

@@ -9,30 +9,34 @@
     
 import Foundation
 
-public class MacOSAsyncTCPServer: TcpServer {
+public class MacOSIO: IO {
+    
+    private var backlog = Dictionary<Int32, Array<(chunk: [UInt8], done: ((Void) -> IODoneAction))>>()
     
-    private var backlog = Dictionary<Int32, Array<(chunk: [UInt8], done: ((Void) -> TcpWriteDoneAction))>>()
     private var peers = Set<Int32>()
     
     private let kernelQueue: KernelQueue
+    
     private let server: UInt
     
     public required init(_ port: in_port_t, forceIPv4: Bool, bindAddress: String? = nil) throws {
         
         self.kernelQueue = try KernelQueue()
         
-        self.server = UInt(try MacOSAsyncTCPServer.nonBlockingSocketForListenening(port))
+        self.server = UInt(
+            try MacOSIO.nonBlockingSocketForListenening(port, forceIPv4: forceIPv4, address: bindAddress)
+        )
         
         self.kernelQueue.subscribe(server, .read)
     }
     
-    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> TcpWriteDoneAction)) throws {
+    public func write(_ socket: Int32, _ data: Array<UInt8>, _ done: @escaping ((Void) -> IODoneAction)) throws {
         
         let result = Darwin.write(socket, data, data.count)
         
         if result == -1 {
             defer { self.finish(socket) }
-            throw AsyncError.writeFailed(Process.error)
+            throw SwifterError.writeFailed(Process.error)
         }
         
         if result == data.count {
@@ -46,12 +50,12 @@ public class MacOSAsyncTCPServer: TcpServer {
         self.kernelQueue.resume(UInt(socket), .write)
     }
     
-    public func wait(_ callback: ((TcpServerEvent) -> Void)) throws {
+    public func wait(_ callback: ((IOEvent) -> Void)) throws {
         try self.kernelQueue.wait { signal in
             switch signal.event {
             case .read:
                 if signal.ident == self.server {
-                    let client = try MacOSAsyncTCPServer.acceptAndConfigureClientSocket(Int32(signal.ident))
+                    let client = try MacOSIO.acceptAndConfigureClientSocket(Int32(signal.ident))
                     self.peers.insert(client)
                     self.backlog[Int32(client)] = []
                     kernelQueue.subscribe(UInt(client), .read)
@@ -93,7 +97,7 @@ public class MacOSAsyncTCPServer: TcpServer {
                 self.kernelQueue.pause(signal.ident, .write)
             case .error:
                 if signal.ident == self.server {
-                    throw AsyncError.async(Process.error)
+                    throw SwifterError.async(Process.error)
                 } else {
                     self.finish(Int32(signal.ident))
                     callback(.disconnect("", Int32(signal.ident)))
@@ -120,33 +124,36 @@ public class MacOSAsyncTCPServer: TcpServer {
         let _ = Darwin.close(Int32(server))
     }
     
-    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080) throws -> Int32 {
+    public static func nonBlockingSocketForListenening(_ port: in_port_t = 8080, forceIPv4: Bool = false, address: String? = nil) throws -> Int32 {
         
-        let server = Darwin.socket(AF_INET, SOCK_STREAM, 0)
+        let server = Darwin.socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0)
         
         guard server != -1 else {
-            throw AsyncError.socketCreation(Process.error)
+            throw SwifterError.socketCreation(Process.error)
         }
         
         var value: Int32 = 1
         if Darwin.setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
             defer { let _ = Darwin.close(server) }
-            throw AsyncError.setReuseAddrFailed(Process.error)
+            throw SwifterError.setReuseAddrFailed(Process.error)
         }
         
-        try setSocketNonBlocking(server)
-        try setSocketNoSigPipe(server)
-        
-        var addr = anyAddrForPort(port)
-        
-        if withUnsafePointer(to: &addr, { Darwin.bind(server, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size)) }) == -1 {
-            defer { let _ = Darwin.close(server) }
-            throw AsyncError.bindFailed(Process.error)
+        do {
+            try setSocketNonBlocking(server)
+            try setSocketNoSigPipe(server)
+            if forceIPv4 {
+                try bind(toSocket: server, port: port, andIPv4Address: address)
+            } else {
+                try bind(toSocket: server, port: port, andAddress: address)
+            }
+        } catch {
+            let _ = Darwin.close(server)
+            throw error
         }
         
         if Darwin.listen(server, SOMAXCONN) == -1 {
             defer { let _ = Darwin.close(server) }
-            throw AsyncError.listenFailed(Process.error)
+            throw SwifterError.listenFailed(Process.error)
         }
         
         return server
@@ -155,7 +162,7 @@ public class MacOSAsyncTCPServer: TcpServer {
     public static func acceptAndConfigureClientSocket(_ socket: Int32) throws -> Int32 {
         
         guard case let client = Darwin.accept(socket, nil, nil), client != -1 else {
-            throw AsyncError.acceptFailed(Process.error)
+            throw SwifterError.acceptFailed(Process.error)
         }
         
         try self.setSocketNonBlocking(client)
@@ -164,26 +171,70 @@ public class MacOSAsyncTCPServer: TcpServer {
         return client
     }
     
-    public static func anyAddrForPort(_ port: in_port_t) -> sockaddr_in {
+    public static func bind(toSocket socket: Int32, port: in_port_t, andIPv4Address address: String? = nil) throws  {
+        
         var addr = sockaddr_in()
+        
         addr.sin_len = __uint8_t(MemoryLayout<sockaddr_in>.size)
         addr.sin_family = sa_family_t(AF_INET)
         addr.sin_port = port.bigEndian
-        addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        
+        if let addressFound = address {
+            guard addressFound.withCString({ inet_pton(AF_INET, $0, &addr.sin_addr) }) == 1 else {
+                throw SwifterError.inetPtonFailed(Errno.description())
+            }
+        } else {
+            addr.sin_addr = in_addr(s_addr: in_addr_t(0))
+        }
+        
         addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
-        return addr
+
+        let bindResult = withUnsafePointer(to: &addr) {
+            Darwin.bind(socket, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
+        }
+        
+        guard bindResult != -1 else {
+             throw SwifterError.bindFailed(Errno.description())
+        }
+    }
+    
+    public static func bind(toSocket socket: Int32, port: in_port_t, andAddress address: String? = nil) throws {
+        
+        var addr = sockaddr_in6()
+        
+        addr.sin6_len = __uint8_t(MemoryLayout<sockaddr_in6>.size)
+        addr.sin6_family = sa_family_t(AF_INET6)
+        addr.sin6_port = port.bigEndian
+        
+        if let addressFound = address {
+            guard addressFound.withCString({ inet_pton(AF_INET6, $0, &addr.sin6_addr) }) == 1 else {
+                throw SwifterError.inetPtonFailed(Errno.description())
+            }
+        } else {
+            addr.sin6_addr = in6addr_any
+        }
+        
+        addr.sin6_scope_id = 0
+        
+        let bindResult = withUnsafePointer(to: &addr) {
+            Darwin.bind(socket, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in6>.size))
+        }
+        
+        guard bindResult != -1 else {
+            throw SwifterError.bindFailed(Errno.description())
+        }
     }
     
     public static func setSocketNonBlocking(_ socket: Int32) throws {
         if Darwin.fcntl(socket, F_SETFL, Darwin.fcntl(socket, F_GETFL, 0) | O_NONBLOCK) == -1 {
-            throw AsyncError.setNonBlockFailed(Process.error)
+            throw SwifterError.setNonBlockFailed(Process.error)
         }
     }
     
     public static func setSocketNoSigPipe(_ socket: Int32) throws {
         var value = 1
         if Darwin.setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
-            throw AsyncError.setNoSigPipeFailed(Process.error)
+            throw SwifterError.setNoSigPipeFailed(Process.error)
         }
     }
 }
@@ -200,7 +251,7 @@ public class KernelQueue {
     
     public init() throws {
         guard case let queue = kqueue(), queue != -1 else {
-            throw AsyncError.async(Process.error)
+            throw SwifterError.async(Process.error)
         }
         self.queue = queue
     }
@@ -241,14 +292,14 @@ public class KernelQueue {
         
         if !changes.isEmpty {
             if kevent(self.queue, &changes, Int32(changes.count), nil, 0, nil) == -1 {
-                throw AsyncError.async(Process.error)
+                throw SwifterError.async(Process.error)
             }
         }
         
         self.changes.removeAll(keepingCapacity: true)
         
         guard case let count = kevent(self.queue, nil, 0, &events, Int32(events.count), nil), count != -1 else {
-            throw AsyncError.async(Process.error)
+            throw SwifterError.async(Process.error)
         }
         
         for event in events[0..<Int(count)] {

+ 1 - 0
Sources/Misc.swift

@@ -45,3 +45,4 @@ public struct Process {
         return String(cString: UnsafePointer(strerror(errno)))
     }
 }
+

+ 0 - 0
Sources/String+SHA1.swift → Sources/SHA1.swift


+ 9 - 10
Sources/Server.swift

@@ -11,19 +11,19 @@ public class Server {
     
     private var processors = [Int32 : IncomingDataProcessor]()
     
-    private let server: TcpServer
+    private let io: IO
     
     public init(_ port: in_port_t = 8080, forceIPv4: Bool = false) throws {
         #if os(Linux)
-            self.server = try LinuxAsyncServer(port, forceIPv4: forceIPv4)
+            self.io = try LinuxAsyncServer(port, forceIPv4: forceIPv4)
         #else
-            self.server = try MacOSAsyncTCPServer(port, forceIPv4: forceIPv4)
+            self.io = try MacOSIO(port, forceIPv4: forceIPv4)
         #endif
     }
     
     public func serve(_ callback: @escaping ((request: Request, responder: @escaping ((Response) -> Void))) -> Void) throws {
         
-        try self.server.wait { event in
+        try self.io.wait { event in
             
             switch event {
                 
@@ -33,19 +33,18 @@ public class Server {
                     callback((request, { response in
                         let keepIOSession = self.supportsKeepAlive(request.headers) || request.httpVersion == .http11
                         var data = [UInt8]()
-                        data.reserveCapacity(1024)
+                        data.reserveCapacity(1024 + response.body.count)
                         data.append(contentsOf: [UInt8]("HTTP/\(request.httpVersion == .http10 ? "1.0" : "1.1") \(response.status) OK\r\n".utf8))
                         for (name, value) in response.headers {
                             data.append(contentsOf: [UInt8]("\(name): \(value)\r\n".utf8))
                         }
-                        if (keepIOSession) {
+                        if keepIOSession {
                             data.append(contentsOf: [UInt8]("Connection: keep-alive\r\n".utf8))
                         }
-                        data.append(contentsOf: [UInt8]("Content-Length: \(response.body.count)\r\n".utf8))
-                        data.append(contentsOf: [13, 10])
+                        data.append(contentsOf: [UInt8]("Content-Length: \(response.body.count)\r\n\r\n".utf8))
                         data.append(contentsOf: response.body)
                         do {
-                            try self.server.write(socket, data) {
+                            try self.io.write(socket, data) {
                                 if let sucessor = response.processingSuccesor {
                                     self.processors[socket] = sucessor
                                     return .continue
@@ -68,7 +67,7 @@ public class Server {
                     try self.processors[socket]?.process(chunk)
                 } catch {
                     self.processors.removeValue(forKey: socket)
-                    self.server.finish(socket)
+                    self.io.finish(socket)
                 }
             }
         }

+ 1 - 13
Sources/String+File.swift

@@ -64,19 +64,7 @@ extension String {
     
     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 {
+    public func openFile(_ path: String, _ mode: String) throws -> File {
         guard let file = path.withCString({ pathPointer in mode.withCString({ fopen(pathPointer, $0) }) }) else {
             throw FileError.error(errno)
         }

+ 0 - 36
Sources/String+Misc.swift

@@ -1,36 +0,0 @@
-//
-//  String+Misc.swift
-//  Swifter
-//
-//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
-//
-
-import Foundation
-
-
-extension String {
-    
-    public func unquote() -> String {
-        var scalars = self.unicodeScalars;
-        if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 {
-            scalars.removeFirst();
-            scalars.removeLast();
-            return String(scalars)
-        }
-        return self
-    }
-}
-
-extension UnicodeScalar {
-    
-    public func asWhitespace() -> UInt8? {
-        if self.value >= 9 && self.value <= 13 {
-            return UInt8(self.value)
-        }
-        if self.value == 32 {
-            return UInt8(self.value)
-        }
-        return nil
-    }
-    
-}

+ 2 - 0
Sources/Swifter.swift

@@ -55,9 +55,11 @@ public class Swifter {
     }
     
     public func loop() throws {
+        
         try self.server.serve { request, responder in
             
             var middlewareResponse: Response? = nil
+            
             for layer in self.middleware {
                 if let responseFound = layer(request) {
                     middlewareResponse = responseFound

+ 35 - 57
XCode/Swifter.xcodeproj/project.pbxproj

@@ -13,14 +13,11 @@
 		0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0858E7F61D68BC2600491CD1 /* PingServer.swift */; };
 		2659FC1A1DADC077003F3930 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
 		269B47891D3AAAE20042D137 /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
-		269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F71D2C44F30030FC98 /* String+Misc.swift */; };
 		269B478E1D3AAAE20042D137 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
-		269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
 		269B47901D3AAAE20042D137 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
 		269B47951D3AAAE20042D137 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
 		269B47961D3AAAE20042D137 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.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 */; };
+		269B47971D3AAAE20042D137 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* SHA1.swift */; };
 		269B47991D3AAAE20042D137 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* 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, ); }; };
@@ -39,11 +36,11 @@
 		7C0324311E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
 		7C0324321E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
 		7C0324331E51D21100325E4F /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C03241F1E51D07A00325E4F /* Server.swift */; };
-		7C0324341E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
-		7C0324351E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
-		7C0324361E51D21500325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
-		7C0324371E51D21600325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
-		7C0324381E51D21600325E4F /* Tcp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* Tcp.swift */; };
+		7C0324341E51D21500325E4F /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* IO.swift */; };
+		7C0324351E51D21500325E4F /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* IO.swift */; };
+		7C0324361E51D21500325E4F /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* IO.swift */; };
+		7C0324371E51D21600325E4F /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* IO.swift */; };
+		7C0324381E51D21600325E4F /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324251E51D0F900325E4F /* IO.swift */; };
 		7C0324391E51D21800325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
 		7C03243A1E51D21900325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
 		7C03243B1E51D21900325E4F /* Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0324231E51D0A400325E4F /* Linux.swift */; };
@@ -66,16 +63,15 @@
 		7C377E191D964B9F009C6148 /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C377E161D964B6A009C6148 /* String+File.swift */; };
 		7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
 		7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
+		7C4965331E6098C50043503A /* Http+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4965311E6098C50043503A /* Http+Misc.swift */; };
+		7C4965341E6098C50043503A /* Http+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4965311E6098C50043503A /* Http+Misc.swift */; };
+		7C4965351E6098C50043503A /* Http+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4965311E6098C50043503A /* Http+Misc.swift */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
 		7C73C6921C26179C00AEF6CA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB80C1BE2A1D400C8A977 /* AppDelegate.swift */; };
-		7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
-		7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
 		7C76B70D1D2C456A0030FC98 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
 		7C76B70E1D2C456B0030FC98 /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EA1D2C44F30030FC98 /* Demo.swift */; };
 		7C76B7111D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
 		7C76B7121D2C45710030FC98 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
-		7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
-		7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
 		7C76B7191D2C457C0030FC98 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
 		7C76B71A1D2C457C0030FC98 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
 		7C76B71B1D2C457E0030FC98 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
@@ -84,10 +80,8 @@
 		7C76B7221D2C45870030FC98 /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
 		7C76B7251D2C458C0030FC98 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* Base64.swift */; };
 		7C76B7261D2C458D0030FC98 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* 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 */; };
+		7C76B7291D2C45920030FC98 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* SHA1.swift */; };
+		7C76B72A1D2C45920030FC98 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* SHA1.swift */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
 		7CAB377C1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
 		7CAB377D1E51E7D300FED085 /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB377B1E51E7D300FED085 /* WebSockets.swift */; };
@@ -106,14 +100,11 @@
 		7CDAB8141BE2A1D400C8A977 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7CDAB80F1BE2A1D400C8A977 /* Images.xcassets */; };
 		7CDAB8161BE2A1D400C8A977 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAB8111BE2A1D400C8A977 /* ViewController.swift */; };
 		7CEBB86F1D94612D00370A6B /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EC1D2C44F30030FC98 /* Files.swift */; };
-		7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */; };
 		7CEBB8731D94612D00370A6B /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F01D2C44F30030FC98 /* Router.swift */; };
 		7CEBB8741D94612D00370A6B /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F11D2C44F30030FC98 /* Swifter.swift */; };
 		7CEBB8771D94612D00370A6B /* Html.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F41D2C44F30030FC98 /* Html.swift */; };
 		7CEBB87B1D94612D00370A6B /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F61D2C44F30030FC98 /* 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 */; };
-		7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B2A11D369C9D00D35BFB /* Errno.swift */; };
+		7CEBB87D1D94612D00370A6B /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C76B6F81D2C44F30030FC98 /* SHA1.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -171,20 +162,18 @@
 		7C03241F1E51D07A00325E4F /* Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
 		7C0324211E51D09900325E4F /* MacOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacOS.swift; sourceTree = "<group>"; };
 		7C0324231E51D0A400325E4F /* Linux.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linux.swift; sourceTree = "<group>"; };
-		7C0324251E51D0F900325E4F /* Tcp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tcp.swift; sourceTree = "<group>"; };
+		7C0324251E51D0F900325E4F /* IO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IO.swift; sourceTree = "<group>"; };
 		7C0324461E51D36900325E4F /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
 		7C377E161D964B6A009C6148 /* String+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+File.swift"; sourceTree = "<group>"; };
 		7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsWebSocketSession.swift; sourceTree = "<group>"; };
-		7C76B2A11D369C9D00D35BFB /* Errno.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errno.swift; sourceTree = "<group>"; };
+		7C4965311E6098C50043503A /* Http+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Http+Misc.swift"; sourceTree = "<group>"; };
 		7C76B6EA1D2C44F30030FC98 /* Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Demo.swift; sourceTree = "<group>"; };
 		7C76B6EC1D2C44F30030FC98 /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = "<group>"; };
-		7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
 		7C76B6F01D2C44F30030FC98 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
 		7C76B6F11D2C44F30030FC98 /* Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifter.swift; sourceTree = "<group>"; };
 		7C76B6F41D2C44F30030FC98 /* Html.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Html.swift; sourceTree = "<group>"; };
 		7C76B6F61D2C44F30030FC98 /* Base64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 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>"; };
+		7C76B6F81D2C44F30030FC98 /* SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SHA1.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>"; };
@@ -288,24 +277,22 @@
 		7C76B6E91D2C44F30030FC98 /* Sources */ = {
 			isa = PBXGroup;
 			children = (
-				7C76B6F61D2C44F30030FC98 /* Base64.swift */,
-				7C76B2A11D369C9D00D35BFB /* Errno.swift */,
-				7C0324461E51D36900325E4F /* Error.swift */,
-				7C03241D1E51CFEF00325E4F /* Misc.swift */,
-				7C76B6F41D2C44F30030FC98 /* Html.swift */,
-				7C76B6F01D2C44F30030FC98 /* Router.swift */,
 				7C76B6F11D2C44F30030FC98 /* Swifter.swift */,
 				7C03241F1E51D07A00325E4F /* Server.swift */,
 				7C03241B1E51CF6600325E4F /* Http.swift */,
+				7C4965311E6098C50043503A /* Http+Misc.swift */,
+				7C76B6F41D2C44F30030FC98 /* Html.swift */,
+				7C76B6F01D2C44F30030FC98 /* Router.swift */,
 				7C0324231E51D0A400325E4F /* Linux.swift */,
-				7C0324251E51D0F900325E4F /* Tcp.swift */,
 				7C0324211E51D09900325E4F /* MacOS.swift */,
+				7C0324251E51D0F900325E4F /* IO.swift */,
+				7C76B6F61D2C44F30030FC98 /* Base64.swift */,
+				7C0324461E51D36900325E4F /* Error.swift */,
+				7C03241D1E51CFEF00325E4F /* Misc.swift */,
 				7C76B6EA1D2C44F30030FC98 /* Demo.swift */,
 				7C76B6EC1D2C44F30030FC98 /* Files.swift */,
 				7CAB377B1E51E7D300FED085 /* WebSockets.swift */,
-				7C76B6EE1D2C44F30030FC98 /* HttpRequest.swift */,
-				7C76B6F71D2C44F30030FC98 /* String+Misc.swift */,
-				7C76B6F81D2C44F30030FC98 /* String+SHA1.swift */,
+				7C76B6F81D2C44F30030FC98 /* SHA1.swift */,
 				7C377E161D964B6A009C6148 /* String+File.swift */,
 			);
 			name = Sources;
@@ -667,21 +654,19 @@
 			files = (
 				7CAB377E1E51E7D300FED085 /* WebSockets.swift in Sources */,
 				269B47891D3AAAE20042D137 /* Html.swift in Sources */,
-				269B478C1D3AAAE20042D137 /* String+Misc.swift in Sources */,
 				7C03243B1E51D21900325E4F /* Linux.swift in Sources */,
 				7C0324311E51D21100325E4F /* Server.swift in Sources */,
 				269B478E1D3AAAE20042D137 /* Swifter.swift in Sources */,
-				269B478F1D3AAAE20042D137 /* HttpRequest.swift in Sources */,
 				269B47901D3AAAE20042D137 /* Demo.swift in Sources */,
 				7C0324291E51D20700325E4F /* Http.swift in Sources */,
 				269B47951D3AAAE20042D137 /* Files.swift in Sources */,
 				2659FC1A1DADC077003F3930 /* String+File.swift in Sources */,
 				7C0324401E51D21D00325E4F /* MacOS.swift in Sources */,
 				269B47961D3AAAE20042D137 /* Router.swift in Sources */,
-				269B47971D3AAAE20042D137 /* String+SHA1.swift in Sources */,
+				269B47971D3AAAE20042D137 /* SHA1.swift in Sources */,
+				7C4965351E6098C50043503A /* Http+Misc.swift in Sources */,
 				7C03242C1E51D20C00325E4F /* Misc.swift in Sources */,
-				269B47981D3AAAE20042D137 /* Errno.swift in Sources */,
-				7C0324361E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324361E51D21500325E4F /* IO.swift in Sources */,
 				7C0324491E51D36900325E4F /* Error.swift in Sources */,
 				269B47991D3AAAE20042D137 /* Base64.swift in Sources */,
 			);
@@ -694,20 +679,18 @@
 				7CAB377C1E51E7D300FED085 /* WebSockets.swift in Sources */,
 				7C377E191D964B9F009C6148 /* String+File.swift in Sources */,
 				7C76B7211D2C45870030FC98 /* Html.swift in Sources */,
-				7C76B7271D2C458F0030FC98 /* String+Misc.swift in Sources */,
 				7C03243D1E51D21A00325E4F /* Linux.swift in Sources */,
 				7C03242F1E51D21000325E4F /* Server.swift in Sources */,
 				7C76B71B1D2C457E0030FC98 /* Swifter.swift in Sources */,
-				7C76B7151D2C45760030FC98 /* HttpRequest.swift in Sources */,
 				7C0324271E51D20700325E4F /* Http.swift in Sources */,
 				7C76B70D1D2C456A0030FC98 /* Demo.swift in Sources */,
 				7C76B7111D2C45710030FC98 /* Files.swift in Sources */,
 				7C0324421E51D21E00325E4F /* MacOS.swift in Sources */,
 				7C76B7191D2C457C0030FC98 /* Router.swift in Sources */,
-				7C76B7291D2C45920030FC98 /* String+SHA1.swift in Sources */,
+				7C76B7291D2C45920030FC98 /* SHA1.swift in Sources */,
+				7C4965331E6098C50043503A /* Http+Misc.swift in Sources */,
 				7C03242A1E51D20B00325E4F /* Misc.swift in Sources */,
-				7C76B2A21D369C9D00D35BFB /* Errno.swift in Sources */,
-				7C0324341E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324341E51D21500325E4F /* IO.swift in Sources */,
 				7C0324471E51D36900325E4F /* Error.swift in Sources */,
 				7C76B7251D2C458C0030FC98 /* Base64.swift in Sources */,
 			);
@@ -720,20 +703,18 @@
 				7CAB377D1E51E7D300FED085 /* WebSockets.swift in Sources */,
 				7C377E181D964B96009C6148 /* String+File.swift in Sources */,
 				7C76B7221D2C45870030FC98 /* Html.swift in Sources */,
-				7C76B7281D2C458F0030FC98 /* String+Misc.swift in Sources */,
 				7C03243C1E51D21A00325E4F /* Linux.swift in Sources */,
 				7C0324301E51D21100325E4F /* Server.swift in Sources */,
 				7C76B71C1D2C457E0030FC98 /* Swifter.swift in Sources */,
-				7C76B7161D2C45760030FC98 /* HttpRequest.swift in Sources */,
 				7C0324281E51D20700325E4F /* Http.swift in Sources */,
 				7C76B70E1D2C456B0030FC98 /* Demo.swift in Sources */,
 				7C76B7121D2C45710030FC98 /* Files.swift in Sources */,
 				7C0324411E51D21D00325E4F /* MacOS.swift in Sources */,
 				7C76B71A1D2C457C0030FC98 /* Router.swift in Sources */,
-				7C76B72A1D2C45920030FC98 /* String+SHA1.swift in Sources */,
+				7C76B72A1D2C45920030FC98 /* SHA1.swift in Sources */,
+				7C4965341E6098C50043503A /* Http+Misc.swift in Sources */,
 				7C03242B1E51D20B00325E4F /* Misc.swift in Sources */,
-				7C76B2A31D369C9D00D35BFB /* Errno.swift in Sources */,
-				7C0324351E51D21500325E4F /* Tcp.swift in Sources */,
+				7C0324351E51D21500325E4F /* IO.swift in Sources */,
 				7C0324481E51D36900325E4F /* Error.swift in Sources */,
 				7C76B7261D2C458D0030FC98 /* Base64.swift in Sources */,
 			);
@@ -769,7 +750,7 @@
 				0858E7F41D68BB2600491CD1 /* IOSafetyTests.swift in Sources */,
 				7C0324441E51D24C00325E4F /* Http.swift in Sources */,
 				7C03242D1E51D20D00325E4F /* Misc.swift in Sources */,
-				7C0324371E51D21600325E4F /* Tcp.swift in Sources */,
+				7C0324371E51D21600325E4F /* IO.swift in Sources */,
 				7C03243F1E51D21D00325E4F /* MacOS.swift in Sources */,
 				7C4785E91C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */,
 				7CCD87721C660B250068099B /* SwifterTestsStringExtensions.swift in Sources */,
@@ -785,17 +766,14 @@
 				7CCB8C5E1D97B852008B9712 /* String+File.swift in Sources */,
 				7CEBB86F1D94612D00370A6B /* Files.swift in Sources */,
 				7C0324451E51D24C00325E4F /* Http.swift in Sources */,
-				7CEBB8711D94612D00370A6B /* HttpRequest.swift in Sources */,
 				7CEBB8731D94612D00370A6B /* Router.swift in Sources */,
 				7CEBB8741D94612D00370A6B /* Swifter.swift in Sources */,
 				7C03242E1E51D20D00325E4F /* Misc.swift in Sources */,
 				7CEBB8771D94612D00370A6B /* Html.swift in Sources */,
 				7CEBB87B1D94612D00370A6B /* Base64.swift in Sources */,
-				7CEBB87C1D94612D00370A6B /* String+Misc.swift in Sources */,
-				7CEBB87D1D94612D00370A6B /* String+SHA1.swift in Sources */,
-				7CEBB87F1D94612D00370A6B /* Errno.swift in Sources */,
+				7CEBB87D1D94612D00370A6B /* SHA1.swift in Sources */,
 				7CAB37801E51E7D300FED085 /* WebSockets.swift in Sources */,
-				7C0324381E51D21600325E4F /* Tcp.swift in Sources */,
+				7C0324381E51D21600325E4F /* IO.swift in Sources */,
 				7CCD87841C660ED60068099B /* SwifterTestsHttpParser.swift in Sources */,
 				7C0324391E51D21800325E4F /* Linux.swift in Sources */,
 				0858E7F91D68BC2600491CD1 /* PingServer.swift in Sources */,