ソースを参照

Fixed linux SPM build

kolakowski 10 年 前
コミット
3d72e2e1f3

+ 6 - 3
Sources/Swifter/App.swift

@@ -51,7 +51,10 @@ public class App {
         try self.server.start(port)
         
         print("Server started. Waiting for requests....")
-        
-        NSRunLoop.main().run()
+        #if os(Linux)
+    	    while true { }
+	#else
+    	    NSRunLoop.current().run()
+	#endif
     }
-}
+}

+ 60 - 0
Sources/Swifter/App.swift~

@@ -0,0 +1,60 @@
+//
+//  App.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class App {
+    
+    private let server = HttpServer()
+
+    public init() { }
+    
+    public func run(port: in_port_t = 9080, _ databasePath: String) throws -> Void {
+        
+        // Open database connection.
+        
+        DatabaseReflection.sharedDatabase = try SQLite.open(databasePath)
+        
+        defer {
+            DatabaseReflection.sharedDatabase?.close()
+        }
+        
+        // Watch process signals.
+        
+        Process.watchSignals { switch $0 {
+            case SIGTERM, SIGINT:
+                self.server.stop()
+                DatabaseReflection.sharedDatabase?.close()
+                exit(EXIT_SUCCESS)
+            case SIGHUP:
+                print("//TODO - Reload config.")
+            default:
+                print("Unknown signal received: \(signal).")
+            }
+        }
+        
+        // Add simple logging.
+        
+        self.server.middleware.append({ r in
+            print("\(r.method) - \(r.path)")
+            return nil
+        })
+        
+        // Boot the server.
+        
+        print("Starting Swifter (\(HttpServer.VERSION)) at port \(port) with PID \(Process.PID)...")
+        
+        try self.server.start(port)
+        
+        print("Server started. Waiting for requests....")
+        #if os(Linux)
+    	    NSRunLoop.currentRunLoop().run()
+	#else
+    	    NSRunLoop.current().run()
+	#endif
+    }
+}

+ 0 - 7
Sources/Swifter/DemoServer.swift

@@ -14,8 +14,6 @@ public func demoServer(_ publicDir: String) -> HttpServer {
     let server = HttpServer()
     
     server["/public/:path"] = HttpHandlers.shareFilesFromDirectory(publicDir)
-    
-    server["/files/:path"] = HttpHandlers.directoryBrowser("/")
 
     server["/"] = { r in
         var listPage = "Available services:<br><ul>"
@@ -87,11 +85,6 @@ public func demoServer(_ publicDir: String) -> HttpServer {
         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(value: 3), NSString(string: "bar"): NSString(string: "baz")]
-        return .OK(.Json(jsonObject))
-    }
-    
     server["/redirect"] = { r in
         return .MovedPermanently("http://www.google.com")
     }

+ 123 - 0
Sources/Swifter/DemoServer.swift~

@@ -0,0 +1,123 @@
+//
+//  DemoServer.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public func demoServer(_ publicDir: String) -> HttpServer {
+    
+    print(publicDir)
+    
+    let server = HttpServer()
+    
+    server["/public/:path"] = HttpHandlers.shareFilesFromDirectory(publicDir)
+    
+    server["/files/:path"] = HttpHandlers.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>"
+            }
+        }
+        listPage += "</ul>"
+        return .OK(.Html(listPage))
+    }
+    
+    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)"))
+    }
+    
+    server.GET["/upload"] = { r in
+        if let html = NSData(contentsOfFile:"\(publicDir)/file.html") {
+            var array = [UInt8](repeating: 0, count: html.length)
+            html.getBytes(&array, length: html.length)
+            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+        }
+        return .NotFound
+    }
+    
+    server.POST["/upload"] = { r in
+        var response = ""
+        for multipart in r.parseMultiPartFormData() {
+            response += "Name: \(multipart.name) File name: \(multipart.fileName) Size: \(multipart.body.count)<br>"
+        }
+        return HttpResponse.OK(.Html(response))
+    }
+    
+    server.GET["/login"] = { r in
+        if let html = NSData(contentsOfFile:"\(publicDir)/login.html") {
+            var array = [UInt8](repeating: 0, count: html.length)
+            html.getBytes(&array, length: html.length)
+            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+        }
+        return .NotFound
+    }
+    
+    server.POST["/login"] = { r in
+        let formFields = r.parseUrlencodedForm()
+        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["/raw"] = { r in
+        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], { $0.write([UInt8]("test".utf8)) })
+    }
+    
+    server["/redirect"] = { r in
+        return .MovedPermanently("http://www.google.com")
+    }
+
+    server["/long"] = { r in
+        var longResponse = ""
+        for k in 0..<1000 { longResponse += "(\(k)),->" }
+        return .OK(.Html(longResponse))
+    }
+    
+    server["/wildcard/*/test/*/:param"] = { r in
+        return .OK(.Html(r.path))
+    }
+    
+    server["/stream"] = { r in
+        return HttpResponse.RAW(200, "OK", nil, { w in
+            for i in 0...100 {
+                w.write([UInt8]("[chunk \(i)]".utf8));
+            }
+        })
+    }
+    
+    server["/websocket-echo"] = HttpHandlers.websocket({ (session, text) in
+        session.writeText(text)
+    }, { (session, binary) in
+        session.writeBinary(binary)
+    })
+    
+    server.get("/get-via-closure") { r in
+        return .OK(.Html("GET OK"))
+    }
+    
+    return server
+}

+ 1 - 97
Sources/Swifter/HttpHandlers+Files.swift

@@ -27,100 +27,4 @@ extension HttpHandlers {
             })
         }
     }
-    
-    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.substring(from: rangeHeader.startIndex.advanced(by: 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](repeating: 0, count: fileBody.length)
-                    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.subdata(with: chunkRange)
-                
-                let headers = [ "Content-Range" : "bytes \(startStr)-\(endStr)/\(fileBody.length)" ]
-                
-                var content = [UInt8](repeating: 0, count: chunk.length)
-                chunk.getBytes(&content, length: chunk.length)
-                return HttpResponse.RAW(206, "Partial Content", headers, { $0.write(content) })
-            } else {
-                var content = [UInt8](repeating: 0, count: fileBody.length)
-                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.fileExists(atPath: filePath, isDirectory: &isDir) else {
-                return HttpResponse.NotFound
-            }
-            if isDir {
-                do {
-                    let files = try fileManager.contentsOfDirectory(atPath: filePath)
-                    var response = "<h3>\(filePath)</h3></br><table>"
-                    response += files.map({ "<tr><td><a href=\"\(r.path)/\($0)\">\($0)</a></td></tr>"}).joined(separator: "")
-                    response += "</table>"
-                    return HttpResponse.OK(.Html(response))
-                } catch {
-                    return HttpResponse.NotFound
-                }
-            } else {
-                if let content = NSData(contentsOfFile: filePath) {
-                    var array = [UInt8](repeating: 0, count: content.length)
-                    content.getBytes(&array, length: content.length)
-                    return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
-                }
-                return HttpResponse.NotFound
-            }
-        }
-    }
-}
+}

+ 62 - 0
Sources/Swifter/HttpHandlers+Files.swift~

@@ -0,0 +1,62 @@
+//
+//  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 fileRelativePath = r.params.first else {
+                return .NotFound
+            }
+            let absolutePath = directoryPath + "/" + fileRelativePath.1
+            guard let file = try? File.openForReading(absolutePath) else {
+                return .NotFound
+            }
+            return .RAW(200, "OK", [:], { writer in
+                var buffer = [UInt8](repeating: 0, count: 64)
+                while let count = try? file.read(&buffer) where count > 0 {
+                    writer.write(buffer[0..<count])
+                }
+                file.close()
+            })
+        }
+    }
+    
+    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.fileExists(atPath: filePath, isDirectory: &isDir) else {
+                return HttpResponse.NotFound
+            }
+            if isDir {
+                do {
+                    let files = try fileManager.contentsOfDirectory(atPath: filePath)
+                    var response = "<h3>\(filePath)</h3></br><table>"
+                    response += files.map({ "<tr><td><a href=\"\(r.path)/\($0)\">\($0)</a></td></tr>"}).joined(separator: "")
+                    response += "</table>"
+                    return HttpResponse.OK(.Html(response))
+                } catch {
+                    return HttpResponse.NotFound
+                }
+            } else {
+                if let content = NSData(contentsOfFile: filePath) {
+                    var array = [UInt8](repeating: 0, count: content.length)
+                    content.getBytes(&array, length: content.length)
+                    return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
+                }
+                return HttpResponse.NotFound
+            }
+        }
+    }
+}

+ 15 - 8
Sources/Swifter/HttpResponse.swift

@@ -29,14 +29,21 @@ public enum HttpResponseBody {
         do {
             switch self {
             case .Json(let object):
-                guard NSJSONSerialization.isValidJSONObject(object) else {
-                    throw SerializationError.InvalidObject
-                }
-                let json = try NSJSONSerialization.data(withJSONObject: object, options: NSJSONWritingOptions.prettyPrinted)
-                let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
-                return (data.count, {
-                    $0.write(data)
-                })
+		#if os(Linux)
+    	    	let data = [UInt8]("Not ready for Linux.".utf8)
+		return (data.count, {
+		    $0.write(data)
+		})
+		#else
+		        guard NSJSONSerialization.isValidJSONObject(object) else {
+		            throw SerializationError.InvalidObject
+		        }
+		        let json = try NSJSONSerialization.data(withJSONObject: object, options: NSJSONWritingOptions.prettyPrinted)
+		        let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
+		        return (data.count, {
+		            $0.write(data)
+		        })
+		#endif
             case .Text(let body):
                 let data = [UInt8](body.utf8)
                 return (data.count, {

+ 175 - 0
Sources/Swifter/HttpResponse.swift~

@@ -0,0 +1,175 @@
+//
+//  HttpResponse.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public enum SerializationError: ErrorProtocol {
+    case InvalidObject
+    case NotSupported
+}
+
+public protocol HttpResponseBodyWriter {
+    func write(_ data: [UInt8])
+    func write(_ data: ArraySlice<UInt8>)
+}
+
+public enum HttpResponseBody {
+    
+    case Json(AnyObject)
+    case Html(String)
+    case Text(String)
+    case Data([UInt8])
+    case Custom(Any, (Any) throws -> String)
+    
+    func content() -> (Int, (HttpResponseBodyWriter throws -> Void)?) {
+        do {
+            switch self {
+            case .Json(let object):
+		#if os(Linux)
+    	    	NSRunLoop.currentRunLoop().run()
+		#else
+		        guard NSJSONSerialization.isValidJSONObject(object) else {
+		            throw SerializationError.InvalidObject
+		        }
+		        let json = try NSJSONSerialization.data(withJSONObject: object, options: NSJSONWritingOptions.prettyPrinted)
+		        let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
+		        return (data.count, {
+		            $0.write(data)
+		        })
+		#endif
+            case .Text(let body):
+                let data = [UInt8](body.utf8)
+                return (data.count, {
+                    $0.write(data)
+                })
+            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)
+                })
+            case .Data(let body):
+                return (body.count, {
+                    $0.write(body)
+                })
+            case .Custom(let object, let closure):
+                let serialised = try closure(object)
+                let data = [UInt8](serialised.utf8)
+                return (data.count, {
+                    $0.write(data)
+                })
+            }
+        } catch {
+            let data = [UInt8]("Serialisation error: \(error)".utf8)
+            return (data.count, {
+                $0.write(data)
+            })
+        }
+    }
+}
+
+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)? )
+    
+    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
+        }
+    }
+    
+    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
+        }
+    }
+    
+    func headers() -> [String: String] {
+        var headers = ["Server" : "Swifter \(HttpServer.VERSION)"]
+        switch self {
+        case .SwitchProtocols(let switchHeaders, _):
+            for (key, value) in switchHeaders {
+                headers[key] = value
+            }
+        case .OK(let body):
+            switch body {
+            case .Text(_)   : headers["Content-Type"] = "text/plain"
+            case .Json(_)   : headers["Content-Type"] = "application/json"
+            case .Html(_)   : headers["Content-Type"] = "text/html"
+            case .Data(_)   : headers["Content-Type"] = "application/octet-stream"
+            default:break
+            }
+        case .MovedPermanently(let location):
+            headers["Location"] = location
+        case .RAW(_, _, let rawHeaders, _):
+            if let rawHeaders = rawHeaders {
+                for (k, v) in rawHeaders {
+                    headers.updateValue(v, forKey: k)
+                }
+            }
+        default:break
+        }
+        return headers
+    }
+    
+    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)
+        default                        : return (-1, nil)
+        }
+    }
+    
+    func socketSession() -> (Socket -> Void)?  {
+        switch self {
+        case SwitchProtocols(_, let handler) : return handler
+        default: return nil
+        }
+    }
+}
+
+/**
+    Makes it possible to compare handler responses with '==', but
+	ignores any associated values. This should generally be what
+	you want. E.g.:
+	
+    let resp = handler(updatedRequest)
+        if resp == .NotFound {
+        print("Client requested not found: \(request.url)")
+    }
+*/
+
+func ==(inLeft: HttpResponse, inRight: HttpResponse) -> Bool {
+    return inLeft.statusCode() == inRight.statusCode()
+}
+

+ 1 - 1
Sources/Swifter/HttpServerIO.swift

@@ -153,7 +153,7 @@ public class HttpServerIO {
     func dispatch_async(_ queueId: Int, _ block: (Void -> Void)) {
         let unmanagedDispatchContext = Unmanaged.passRetained(dispatch_context(block))
         let context = UnsafeMutablePointer<Void>(OpaquePointer(bitPattern: unmanagedDispatchContext))
-        var pthread: pthread_t?
+        var pthread: pthread_t = 0
         pthread_create(&pthread, nil, { (context: UnsafeMutablePointer<Void>!) -> UnsafeMutablePointer<Void>! in
             let unmanaged = Unmanaged<dispatch_context>.fromOpaque(OpaquePointer(context))
             unmanaged.takeUnretainedValue().block()

+ 165 - 0
Sources/Swifter/HttpServerIO.swift~

@@ -0,0 +1,165 @@
+//
+//  HttpServer.swift
+//  Swifter
+//
+//  Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public class HttpServerIO {
+    
+    private var listenSocket: Socket = Socket(socketFileDescriptor: -1)
+    private var clientSockets: Set<Socket> = []
+    private let clientSocketsLock = NSLock()
+    
+    public typealias MiddlewareCallback = HttpRequest -> HttpResponse?
+    
+    public var middleware = [MiddlewareCallback]()
+    
+    public func start(_ listenPort: in_port_t = 8080) throws {
+        stop()
+        listenSocket = try Socket.tcpSocketForListen(listenPort)
+        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), {
+                    self.handleConnection(socket)
+                    self.lock(self.clientSocketsLock) {
+                        self.clientSockets.remove(socket)
+                    }
+                })
+            }
+            self.stop()
+        }
+        
+    }
+    
+    public func stop() {
+        listenSocket.release()
+        lock(self.clientSocketsLock) {
+            for socket in self.clientSockets {
+                socket.shutdwn()
+            }
+            self.clientSockets.removeAll(keepingCapacity: true)
+        }
+    }
+    
+    public func dispatch(_ method: String, path: String) -> ([String: String], HttpRequest -> HttpResponse) {
+        return ([:], { _ in HttpResponse.NotFound })
+    }
+    
+    private func handleConnection(_ socket: Socket) {
+        let address = try? socket.peername()
+        let parser = HttpParser()
+        while let request = try? parser.readHttpRequest(socket) {
+            request.address = address
+            var response = askMiddlewareForResponse(request)
+            if response == nil {
+                let (params, handler) = self.dispatch(request.method, path: request.path)
+                request.params = params
+                response = handler(request)
+            }
+            var keepConnection = parser.supportsKeepAlive(request.headers)
+            do {
+                keepConnection = try self.respond(socket, response: response!, keepAlive: keepConnection)
+            } catch {
+                print("Failed to send response: \(error)")
+                break
+            }
+            if let session = response!.socketSession() {
+                session(socket)
+                break
+            }
+            if !keepConnection { break }
+        }
+        socket.release()
+    }
+
+    private func askMiddlewareForResponse(_ request: HttpRequest) -> HttpResponse? {
+        for layer in middleware {
+            if let response = layer(request) {
+                return response
+            }
+        }
+        return nil
+    }
+    
+    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(_ data: ArraySlice<UInt8>) {
+            do {
+                try socket.writeUInt8(data)
+            } catch {
+                print("\(error)")
+            }
+        }
+    }
+    
+    private func respond(_ socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
+        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
+    
+    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>(OpaquePointer(bitPattern: unmanagedDispatchContext))
+        var pthread: pthread_t
+        pthread_create(&pthread, nil, { (context: UnsafeMutablePointer<Void>!) -> UnsafeMutablePointer<Void>! in
+            let unmanaged = Unmanaged<dispatch_context>.fromOpaque(OpaquePointer(context))
+            unmanaged.takeUnretainedValue().block()
+            unmanaged.release()
+            return context
+            }, context)
+    }
+    
+#endif