소스 검색

Introduced closure-based response's body writer protocol.

Damian Kołakowski 10 년 전
부모
커밋
cec9a93c2f

+ 12 - 6
Sources/DemoServer.swift

@@ -50,7 +50,7 @@ public func demoServer(publicDir: String?) -> HttpServer {
         if let rootDir = publicDir, html = NSData(contentsOfFile:"\(rootDir)/file.html") {
             var array = [UInt8](count: html.length, repeatedValue: 0)
             html.getBytes(&array, length: html.length)
-            return HttpResponse.RAW(200, "OK", nil, array)
+            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
         }
         return .NotFound
     }
@@ -65,11 +65,10 @@ public func demoServer(publicDir: String?) -> HttpServer {
     
     server.GET["/login"] = { r in
         if let rootDir = publicDir, html = NSData(contentsOfFile:"\(rootDir)/login.html") {
-                var array = [UInt8](count: html.length, repeatedValue: 0)
-                html.getBytes(&array, length: html.length)
-                return HttpResponse.RAW(200, "OK", nil, array)
+            var array = [UInt8](count: html.length, repeatedValue: 0)
+            html.getBytes(&array, length: html.length)
+            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
         }
-        
         return .NotFound
     }
     
@@ -83,7 +82,7 @@ public func demoServer(publicDir: String?) -> HttpServer {
     }
     
     server["/raw"] = { r in
-        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], [UInt8]("Sample Response".utf8))
+        return HttpResponse.RAW(200, "OK", ["XXX-Custom-Header": "value"], { $0.write([UInt8]("test".utf8)) })
     }
     
     server["/json"] = { r in
@@ -104,6 +103,13 @@ public func demoServer(publicDir: String?) -> HttpServer {
     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));
+            }
+        })
+    }
 
     return server
 }

+ 4 - 4
Sources/HttpHandlers.swift

@@ -45,7 +45,7 @@ public class HttpHandlers {
                 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, array)
+                    return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
                 }
                 
                 let length = end - start
@@ -63,13 +63,13 @@ public class HttpHandlers {
                 
                 var array = [UInt8](count: subData.length, repeatedValue: 0)
                 subData.getBytes(&array, length: subData.length)
-                return HttpResponse.RAW(206, "Partial Content", headers, array)
+                return HttpResponse.RAW(206, "Partial Content", headers, { $0.write(array) })
                 
             }
             else {
                 var array = [UInt8](count: fileBody.length, repeatedValue: 0)
                 fileBody.getBytes(&array, length: fileBody.length)
-                return HttpResponse.RAW(200, "OK", nil, array)
+                return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
             }
             
         }
@@ -96,7 +96,7 @@ public class HttpHandlers {
                         if let fileBody = NSData(contentsOfFile: filePath) {
                             var array = [UInt8](count: fileBody.length, repeatedValue: 0)
                             fileBody.getBytes(&array, length: fileBody.length)
-                            return HttpResponse.RAW(200, "OK", nil, array)
+                            return HttpResponse.RAW(200, "OK", nil, { $0.write(array) })
                         }
                     }
                 }

+ 30 - 12
Sources/HttpResponse.swift

@@ -11,6 +11,10 @@ public enum SerializationError: ErrorType {
     case NotSupported
 }
 
+public protocol HttpResponseBodyWriter {
+    func write(data: [UInt8])
+}
+
 public enum HttpResponseBody {
     
     case Json(AnyObject)
@@ -18,7 +22,7 @@ public enum HttpResponseBody {
     case Text(String)
     case Custom(Any, (Any) throws -> String)
     
-    func data() -> [UInt8]? {
+    func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) {
         do {
             switch self {
             case .Json(let object):
@@ -26,19 +30,33 @@ public enum HttpResponseBody {
                     throw SerializationError.InvalidObject
                 }
                 let json = try NSJSONSerialization.dataWithJSONObject(object, options: NSJSONWritingOptions.PrettyPrinted)
-                return Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
+                let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
+                return (data.count, {
+                    $0.write(data)
+                })
             case .Text(let body):
-                let serialised = body
-                return [UInt8](serialised.utf8)
+                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>"
-                return [UInt8](serialised.utf8)
+                let data = [UInt8](serialised.utf8)
+                return (data.count, {
+                    $0.write(data)
+                })
             case .Custom(let object, let closure):
                 let serialised = try closure(object)
-                return [UInt8](serialised.utf8)
+                let data = [UInt8](serialised.utf8)
+                return (data.count, {
+                    $0.write(data)
+                })
             }
         } catch {
-            return [UInt8]("Serialisation error: \(error)".utf8)
+            let data = [UInt8]("Serialisation error: \(error)".utf8)
+            return (data.count, {
+                $0.write(data)
+            })
         }
     }
 }
@@ -49,7 +67,7 @@ public enum HttpResponse {
     case MovedPermanently(String)
     case BadRequest, Unauthorized, Forbidden, NotFound
     case InternalServerError
-    case RAW(Int, String, [String:String]?, [UInt8]?)
+    case RAW(Int, String, [String:String]?, ((HttpResponseBodyWriter) -> Void)? )
     
     func statusCode() -> Int {
         switch self {
@@ -103,11 +121,11 @@ public enum HttpResponse {
         return headers
     }
     
-    func body() -> [UInt8]? {
+    func content() -> (length: Int, writeClosure: ((HttpResponseBodyWriter) throws -> Void)?) {
         switch self {
-        case .OK(let body)           : return body.data()
-        case .RAW(_, _, _, let data) : return data
-        default                      : return nil
+        case .OK(let body)             : return body.content()
+        case .RAW(_, _, _, let writer) : return (-1, writer)
+        default                        : return (-1, nil)
         }
     }
 }

+ 25 - 9
Sources/HttpServerIO.swift

@@ -41,18 +41,18 @@ public class HttpServerIO {
         let parser = HttpParser()
         while let request = try? parser.readHttpRequest(socket) {
             let request = request
-            let keepAlive = parser.supportsKeepAlive(request.headers)
             let (params, handler) = self.dispatch(request.method, path: request.path)
             request.address = address
             request.params = params;
             let response = handler(request)
+            var keepConnection = parser.supportsKeepAlive(request.headers)
             do {
-                try self.respond(socket, response: response, keepAlive: keepAlive)
+                keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection)
             } catch {
                 print("Failed to send response: \(error)")
                 break
             }
-            if !keepAlive { break }
+            if !keepConnection { break }
         }
         socket.release()
     }
@@ -77,21 +77,37 @@ public class HttpServerIO {
         handle.unlock();
     }
     
-    private func respond(socket: Socket, response: HttpResponse, keepAlive: Bool) throws {
+    private struct InnerWriteContext: HttpResponseBodyWriter {
+        let socket: Socket
+        func write(data: [UInt8]) {
+            try? socket.writeUInt8(data)
+        }
+    }
+    
+    private func respond(socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
         try socket.writeUTF8("HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
         
-        let length = response.body()?.count ?? 0
-        try socket.writeUTF8("Content-Length: \(length)\r\n")
+        let content = response.content()
+        
+        if content.length > 0 {
+            try socket.writeUTF8("Content-Length: \(content.length)\r\n")
+        }
         
-        if keepAlive {
+        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 body = response.body() {
-            try socket.writeUInt8(body)
+    
+        if let writeClosure = content.writeClosure {
+            let context = InnerWriteContext(socket: socket)
+            try writeClosure(context)
         }
+        
+        return keepAlive && content.length != -1;
     }
 }

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