Procházet zdrojové kódy

Added support for multipart requests.
Removed "serialiser" abstraction ( cleaning before a switch to response writer ).

Damian Kołakowski před 10 roky
rodič
revize
8ec765f432

+ 11 - 0
Resources/file.html

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

+ 14 - 0
Sources/Swifter/Const.swift

@@ -0,0 +1,14 @@
+//
+//  Const.swift
+//  Swifter
+//
+//  Created by Damian Kolakowski on 17/12/15.
+//  Copyright © 2015 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+struct Constants {
+    static let CR = UInt8(13)
+    static let NL = UInt8(10)
+}

+ 22 - 2
Sources/Swifter/DemoServer.swift

@@ -39,6 +39,27 @@ public func demoServer(publicDir: String?) -> HttpServer {
         return .OK(.Html("<h3>Address: \(r.address)</h3><h3>Url:</h3> \(r.url)<h3>Method: \(r.method)</h3><h3>Headers:</h3>\(headersInfo)<h3>Query:</h3>\(queryParamsInfo)<h3>Path params:</h3>\(pathParamsInfo)"))
     }
     
+    server["/upload"] = { r in
+        switch r.method.uppercaseString {
+        case "GET":
+            if let rootDir = publicDir {
+                if let 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)
+                } else {
+                    return .NotFound
+                }
+            }
+        case "POST":
+            let formFields = r.parseMultiPartFormData()
+            return HttpResponse.OK(.Html(formFields.map({ UInt8ArrayToUTF8String($0.body) }).joinWithSeparator("<br>")))
+        default:
+            return .NotFound
+        }
+        return .NotFound
+    }
+    
     server["/login"] = { r in
         switch r.method.uppercaseString {
         case "GET":
@@ -52,13 +73,12 @@ public func demoServer(publicDir: String?) -> HttpServer {
                 }
             }
         case "POST":
-            let formFields = r.parseForm()
+            let formFields = r.parseUrlencodedForm()
             return HttpResponse.OK(.Html(formFields.map({ "\($0.0) = \($0.1)" }).joinWithSeparator("<br>")))
         default:
             return .NotFound
         }
         return .NotFound
-
     }
     
     server["/demo"] = { r in

+ 5 - 16
Sources/Swifter/HttpParser.swift

@@ -42,10 +42,10 @@ class HttpParser {
         }
         return query.split("&").map { (param: String) -> (String, String) in
             let tokens = param.split("=")
-            guard tokens.count >= 2 else {
+            guard let name = tokens.first, value = tokens.last else {
                 return ("", "")
             }
-            return (tokens[0].removePercentEncoding(), tokens[1].removePercentEncoding())
+            return (name.removePercentEncoding(), value.removePercentEncoding())
         }
     }
     
@@ -53,11 +53,7 @@ class HttpParser {
         var body = [UInt8]()
         var counter = 0
         while counter < size {
-            let c = socket.read()
-            if c < 0 {
-                throw HttpParserError.ReadBodyFailed(String.fromCString(UnsafePointer(strerror(errno))) ?? "Error: \(errno)")
-            }
-            body.append(UInt8(c))
+            body.append(try socket.read())
             counter++
         }
         return body
@@ -71,15 +67,8 @@ class HttpParser {
                 return requestHeaders
             }
             let headerTokens = headerLine.split(":")
-            if headerTokens.count >= 2 {
-                // RFC 2616 - "Hypertext Transfer Protocol -- HTTP/1.1", paragraph 4.2, "Message Headers":
-                // "Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive."
-                // We will keep lower case version.
-                let headerName = headerTokens[0].lowercaseString
-                let headerValue = headerTokens[1].trim()
-                if !headerName.isEmpty && !headerValue.isEmpty {
-                    requestHeaders.updateValue(headerValue, forKey: headerName)
-                }
+            if let name = headerTokens.first, value = headerTokens.last where headerTokens.count == 2 {
+                requestHeaders[name.lowercaseString] = value.trim()
             }
         } while true
     }

+ 103 - 10
Sources/Swifter/HttpRequest.swift

@@ -16,18 +16,111 @@ public struct HttpRequest {
     public var address: String?
     public var params: [String: String]
     
-    public func parseForm() -> [(String, String)] {
-        if let body = body {
-            return UInt8ArrayToUTF8String(body).split("&").map { (param: String) -> (String, String) in
-                let tokens = param.split("=")
-                if tokens.count >= 2 {
-                    let key = tokens[0].replace("+", new: " ").removePercentEncoding()
-                    let value = tokens[1].replace("+", new: " ").removePercentEncoding()
-                    return (key, value)
-                }
-                return ("","")
+    public func parseUrlencodedForm() -> [(String, String)] {
+        guard let body = body, let contentTypeHeader = headers["content-type"] else {
+            return []
+        }
+        let contentTypeHeaderTokens = contentTypeHeader.split(";").map { $0.trim() }
+        guard let contentType = contentTypeHeaderTokens.first where contentType == "application/x-www-form-urlencoded" else {
+            return []
+        }
+        return UInt8ArrayToUTF8String(body).split("&").map { (param: String) -> (String, String) in
+            let tokens = param.split("=")
+            if let name = tokens.first, value = tokens.last where tokens.count == 2 {
+                return (name.replace("+", new: " ").removePercentEncoding(),
+                        value.replace("+", new: " ").removePercentEncoding())
+            }
+            return ("","")
+        }
+    }
+    
+    public struct MultiPart {
+        public let headers: [String: String]
+        public let body: [UInt8]
+    }
+    
+    public func parseMultiPartFormData() -> [MultiPart] {
+        guard let body = body, let contentTypeHeader = headers["content-type"] else {
+            return []
+        }
+        let contentTypeHeaderTokens = contentTypeHeader.split(";").map { $0.trim() }
+        guard let contentType = contentTypeHeaderTokens.first where contentType == "multipart/form-data" else {
+            return []
+        }
+        var boundary: String? = nil
+        contentTypeHeaderTokens.forEach({
+            let tokens = $0.split("=")
+            if let key = tokens.first where key == "boundary" && tokens.count == 2 {
+                boundary = tokens.last
             }
+        })
+        if let boundary = boundary where boundary.utf8.count > 0 {
+            return parseMultiPartFormData(body, boundary: "--\(boundary)")
         }
         return []
     }
+    
+    private func parseMultiPartFormData(data: [UInt8], boundary: String) -> [MultiPart] {
+        var generator = data.generate()
+        var result = [MultiPart]()
+        while let part = nextMultiPart(&generator, boundary: boundary, isFirst: result.isEmpty) {
+            result.append(part)
+        }
+        return result
+    }
+    
+    private func nextMultiPart(inout generator: IndexingGenerator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? {
+        if isFirst {
+            guard nextMultiPartLine(&generator) == boundary else {
+                return nil
+            }
+        } else {
+            nextMultiPartLine(&generator)
+        }
+        var headers = [String: String]()
+        while let line = nextMultiPartLine(&generator) where !line.isEmpty {
+            let tokens = line.split(":")
+            if let name = tokens.first, value = tokens.last where tokens.count == 2 {
+                headers[name.lowercaseString] = value.trim()
+            }
+        }
+        guard let body = nextMultiPartBody(&generator, boundary: boundary) else {
+            return nil
+        }
+        return MultiPart(headers: headers, body: body)
+    }
+    
+    private func nextMultiPartLine(inout generator: IndexingGenerator<[UInt8]>) -> String? {
+        var result = String()
+        while let value = generator.next() {
+            if value > Constants.CR {
+                result.append(Character(UnicodeScalar(value)))
+            }
+            if value == Constants.NL {
+                break
+            }
+        }
+        return result
+    }
+    
+    private func nextMultiPartBody(inout generator: IndexingGenerator<[UInt8]>, boundary: String) -> [UInt8]? {
+        var body = [UInt8]()
+        let boundaryArray = [UInt8](boundary.utf8)
+        var matchOffset = 0;
+        while let x = generator.next() {
+            matchOffset = ( x == boundaryArray[matchOffset] ? matchOffset + 1 : 0 )
+            body.append(x)
+            if matchOffset == boundaryArray.count {
+                body.removeRange(Range<Int>(start: body.count-matchOffset, end: body.count))
+                if body.last == Constants.NL {
+                    body.removeLast()
+                    if body.last == Constants.CR {
+                        body.removeLast()
+                    }
+                }
+                return body
+            }
+        }
+        return nil
+    }
 }

+ 33 - 93
Sources/Swifter/HttpResponse.swift

@@ -12,88 +12,30 @@ public enum SerializationError: ErrorType {
     case EncodingError
 }
 
-public protocol Serializer {
-    func serialize(object: Any) throws -> String
-}
-
-public class JSONSerializer: Serializer {
-    public func serialize(object: Any) throws -> String {
-        guard let obj = object as? AnyObject where NSJSONSerialization.isValidJSONObject(obj) else {
-            throw SerializationError.InvalidObject
-        }
-        
-        let json = try NSJSONSerialization.dataWithJSONObject(obj, options: NSJSONWritingOptions.PrettyPrinted)
-        
-        return String(json)
-    }
-    
-    private static func serialize(object: Any) throws -> String {
-        let serializer = JSONSerializer()
-        return try serializer.serialize(object)
-    }
-}
-
-public class XMLSerializer: Serializer {
-    public func serialize(object: Any) throws -> String {
-        throw SerializationError.NotSupported
-    }
-    
-    private static func serialize(object: Any) throws -> String {
-        let serializer = XMLSerializer()
-        return try serializer.serialize(object)
-    }
-}
-
-public class PLISTSerializer: Serializer {
-    public func serialize(object: Any) throws -> String {
-        let format = NSPropertyListFormat.XMLFormat_v1_0
-        
-        guard let obj = object as? AnyObject where NSPropertyListSerialization.propertyList(obj, isValidForFormat: format) else {
-            throw SerializationError.InvalidObject
-        }
-        
-        let plist = try NSPropertyListSerialization.dataWithPropertyList(obj, format: format, options: 0)
-        
-        
-        
-        return String(plist)
-    }
-    
-    private static func serialize(object: Any) throws -> String {
-        let serializer = PLISTSerializer()
-        return try serializer.serialize(object)
-    }
-}
-
 public enum HttpResponseBody {
     
     case Json(AnyObject)
-    case Xml(AnyObject)
-    case Plist(AnyObject)
     case Html(String)
     case Text(String)
-    case Custom(Serializer, Any)
+    case Custom(Any, (Any) throws -> String)
     
     func data() -> [UInt8]? {
         do {
             switch self {
             case .Json(let object):
-                let serialised = try JSONSerializer.serialize(object)
-                return [UInt8](serialised.utf8)
-            case .Xml(let object):
-                let serialised = try XMLSerializer.serialize(object)
-                return [UInt8](serialised.utf8)
-            case .Plist(let object):
-                let serialised = try PLISTSerializer.serialize(object)
-                return [UInt8](serialised.utf8)
+                guard let obj = object as? AnyObject where NSJSONSerialization.isValidJSONObject(obj) else {
+                    throw SerializationError.InvalidObject
+                }
+                let json = try NSJSONSerialization.dataWithJSONObject(obj, options: NSJSONWritingOptions.PrettyPrinted)
+                return Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
             case .Text(let body):
                 let serialised = body
                 return [UInt8](serialised.utf8)
             case .Html(let body):
                 let serialised = "<html><meta charset=\"UTF-8\"><body>\(body)</body></html>"
                 return [UInt8](serialised.utf8)
-            case .Custom(let serializer, let object):
-                let serialised = try serializer.serialize(object)
+            case .Custom(let object, let closure):
+                let serialised = try closure(object)
                 return [UInt8](serialised.utf8)
             }
         } catch {
@@ -112,30 +54,30 @@ public enum HttpResponse {
     
     func statusCode() -> Int {
         switch self {
-        case .OK(_)                 : return 200
-        case .Created               : return 201
-        case .Accepted              : return 202
-        case .MovedPermanently      : return 301
-        case .BadRequest            : return 400
-        case .Unauthorized          : return 401
-        case .Forbidden             : return 403
-        case .NotFound              : return 404
-        case .InternalServerError   : return 500
-        case .RAW(let code, _ , _, _)   : return code
+        case .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 .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 .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
         }
     }
@@ -146,8 +88,6 @@ public enum HttpResponse {
         case .OK(let body):
             switch body {
             case .Json(_)   : headers["Content-Type"] = "application/json"
-            case .Plist(_)  : headers["Content-Type"] = "application/xml"
-            case .Xml(_)    : headers["Content-Type"] = "application/xml"
             case .Html(_)   : headers["Content-Type"] = "text/html"
             default:break
             }
@@ -173,15 +113,15 @@ public enum HttpResponse {
 }
 
 /**
-	Makes it possible to compare handler responses with '==', but
+    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)")
- }
- */
+    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/HttpServer.swift

@@ -56,7 +56,7 @@ public class HttpServer {
                     let httpParser = HttpParser()
                     while let request = try? httpParser.readHttpRequest(socket) {
                         let keepAlive = httpParser.supportsKeepAlive(request.headers)
-                        let response: HttpResponse
+                        var response = HttpResponse.NotFound
                         if let (params, handler) = self.router.select(request.url) {
                             let updatedRequest = HttpRequest(url: request.url, urlParams: request.urlParams, method: request.method, headers: request.headers, body: request.body, address: socketAddress, params: params)
                             response = handler(updatedRequest)

+ 10 - 14
Sources/Swifter/Socket.swift

@@ -116,13 +116,13 @@ public class Socket: Hashable, Equatable {
     }
     
     public func writeUInt8(data: [UInt8]) throws {
-        try data.withUnsafeBufferPointer { pointer in
+        try data.withUnsafeBufferPointer {
             var sent = 0
             while sent < data.count {
                 #if os(Linux)
-                    let s = send(self.socketFileDescriptor, pointer.baseAddress + sent, Int(data.count - sent), Int32(MSG_NOSIGNAL))
+                    let s = send(self.socketFileDescriptor, $0.baseAddress + sent, Int(data.count - sent), Int32(MSG_NOSIGNAL))
                 #else
-                    let s = write(self.socketFileDescriptor, pointer.baseAddress + sent, Int(data.count - sent))
+                    let s = write(self.socketFileDescriptor, $0.baseAddress + sent, Int(data.count - sent))
                 #endif
                 if s <= 0 {
                     throw SocketError.WriteFailed(Socket.descriptionOfLastError())
@@ -132,25 +132,22 @@ public class Socket: Hashable, Equatable {
         }
     }
     
-    public func read() -> Int {
+    public func read() throws -> UInt8 {
         var buffer = [UInt8](count: 1, repeatedValue: 0)
         let next = recv(self.socketFileDescriptor as Int32, &buffer, Int(buffer.count), 0)
         if next <= 0 {
-            return next
+            throw SocketError.RecvFailed(Socket.descriptionOfLastError())
         }
-        return Int(buffer[0])
+        return buffer[0]
     }
     
     public func readLine() throws -> String {
         var characters: String = ""
-        var n = 0
+        var n: UInt8 = 0
         repeat {
-            n = self.read()
-            if n > 13 /* CR */ { characters.append(Character(UnicodeScalar(n))) }
-        } while n > 0 && n != 10 /* NL */
-        if n == -1 {
-            throw SocketError.RecvFailed(Socket.descriptionOfLastError())
-        }
+            n = try self.read()
+            if n > Constants.CR { characters.append(Character(UnicodeScalar(n))) }
+        } while n != Constants.NL
         return characters
     }
     
@@ -211,7 +208,6 @@ public class Socket: Hashable, Equatable {
     }
 }
 
-
 public func ==(socket1: Socket, socket2: Socket) -> Bool {
     return socket1.socketFileDescriptor == socket2.socketFileDescriptor
 }

+ 21 - 1
Swifter.xcodeproj/project.pbxproj

@@ -11,12 +11,20 @@
 		7AE893EA1C05127900A29F63 /* SwifteriOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893E91C05127900A29F63 /* SwifteriOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE893FE1C0512C400A29F63 /* SwifterMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AE893FD1C0512C400A29F63 /* SwifterMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */; };
+		7C47894C1C222C7F00586CD0 /* Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C47894B1C222C7F00586CD0 /* Const.swift */; };
+		7C47894D1C222C7F00586CD0 /* Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C47894B1C222C7F00586CD0 /* Const.swift */; };
+		7C47894E1C222C7F00586CD0 /* Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C47894B1C222C7F00586CD0 /* Const.swift */; };
+		7C47894F1C222C7F00586CD0 /* Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C47894B1C222C7F00586CD0 /* Const.swift */; };
 		7C67C3471C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
 		7C67C3481C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
 		7C67C3491C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
 		7C67C34A1C17542E007B98E8 /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C67C3461C17542E007B98E8 /* HttpRouter.swift */; };
 		7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 98630C061A1C9A9D00478D08 /* login.html */; };
 		7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CB102DF1A17381D00CBA3B4 /* logo.png */; };
+		7C7488781C1DA07300CBCD77 /* file.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
+		7C7488791C1DA07300CBCD77 /* file.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
+		7C74887A1C1DA07300CBCD77 /* file.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
+		7C74887B1C1DA08200CBCD77 /* file.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7C7488771C1DA07300CBCD77 /* file.html */; };
 		7CA4813E19A2EA8D0030B30D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA4813D19A2EA8D0030B30D /* main.swift */; };
 		7CA4815819A2EF2B0030B30D /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = 7CA4815719A2EF2B0030B30D /* test.json */; };
 		7CA4815919A2EF560030B30D /* test.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7CA4815719A2EF2B0030B30D /* test.json */; };
@@ -84,6 +92,7 @@
 			dstPath = "";
 			dstSubfolderSpec = 7;
 			files = (
+				7C74887B1C1DA08200CBCD77 /* file.html in CopyFiles */,
 				7C71C5B11A1EC49B00682BF0 /* logo.png in CopyFiles */,
 				7C71C5B01A1D52F800682BF0 /* login.html in CopyFiles */,
 				7CA4815919A2EF560030B30D /* test.json in CopyFiles */,
@@ -101,7 +110,9 @@
 		7AE893FD1C0512C400A29F63 /* SwifterMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwifterMac.h; sourceTree = "<group>"; };
 		7AE893FF1C0512C400A29F63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7AE8940C1C05151100A29F63 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
+		7C47894B1C222C7F00586CD0 /* Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Const.swift; sourceTree = "<group>"; };
 		7C67C3461C17542E007B98E8 /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = "<group>"; };
+		7C7488771C1DA07300CBCD77 /* file.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = file.html; 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>"; };
@@ -211,6 +222,7 @@
 		7CA4815619A2EF2B0030B30D /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				7C7488771C1DA07300CBCD77 /* file.html */,
 				98630C061A1C9A9D00478D08 /* login.html */,
 				7CB102DF1A17381D00CBA3B4 /* logo.png */,
 				7CA4815719A2EF2B0030B30D /* test.json */,
@@ -245,8 +257,9 @@
 				7CEAF84C1C14B29B003252DE /* DemoServer.swift */,
 				7C67C3461C17542E007B98E8 /* HttpRouter.swift */,
 				7CEAF84D1C14B29B003252DE /* HttpHandlers.swift */,
-				7CEAF84E1C14B29B003252DE /* HttpParser.swift */,
 				7CEAF84F1C14B29B003252DE /* HttpRequest.swift */,
+				7C47894B1C222C7F00586CD0 /* Const.swift */,
+				7CEAF84E1C14B29B003252DE /* HttpParser.swift */,
 				7CEAF8501C14B29B003252DE /* HttpResponse.swift */,
 				7CEAF8511C14B29B003252DE /* HttpServer.swift */,
 				7CEAF8521C14B29B003252DE /* Socket.swift */,
@@ -399,6 +412,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7C7488791C1DA07300CBCD77 /* file.html in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -406,6 +420,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7C74887A1C1DA07300CBCD77 /* file.html in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -413,6 +428,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7C7488781C1DA07300CBCD77 /* file.html in Resources */,
 				7AE8940D1C05151100A29F63 /* Launch Screen.storyboard in Resources */,
 				7CB102E01A17381D00CBA3B4 /* logo.png in Resources */,
 				7CDAB8141BE2A1D400C8A977 /* Images.xcassets in Resources */,
@@ -438,6 +454,7 @@
 				7CEAF8551C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CD6DABB1C15C48500A04931 /* String+Misc.swift in Sources */,
 				7C67C3491C17542E007B98E8 /* HttpRouter.swift in Sources */,
+				7C47894E1C222C7F00586CD0 /* Const.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -454,6 +471,7 @@
 				7CEAF8561C14B29B003252DE /* DemoServer.swift in Sources */,
 				7CD6DABC1C15C48500A04931 /* String+Misc.swift in Sources */,
 				7C67C34A1C17542E007B98E8 /* HttpRouter.swift in Sources */,
+				7C47894F1C222C7F00586CD0 /* Const.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -466,6 +484,7 @@
 				7CEAF8671C14B29B003252DE /* HttpServer.swift in Sources */,
 				7CD6DAB91C15C48500A04931 /* String+Misc.swift in Sources */,
 				7C67C3471C17542E007B98E8 /* HttpRouter.swift in Sources */,
+				7C47894C1C222C7F00586CD0 /* Const.swift in Sources */,
 				7CEAF8571C14B29B003252DE /* HttpHandlers.swift in Sources */,
 				7CEAF85B1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF86B1C14B29B003252DE /* Socket.swift in Sources */,
@@ -484,6 +503,7 @@
 				7CEAF8681C14B29B003252DE /* HttpServer.swift in Sources */,
 				7CD6DABA1C15C48500A04931 /* String+Misc.swift in Sources */,
 				7C67C3481C17542E007B98E8 /* HttpRouter.swift in Sources */,
+				7C47894D1C222C7F00586CD0 /* Const.swift in Sources */,
 				7CEAF8581C14B29B003252DE /* HttpHandlers.swift in Sources */,
 				7CEAF85C1C14B29B003252DE /* HttpParser.swift in Sources */,
 				7CEAF86C1C14B29B003252DE /* Socket.swift in Sources */,

binární
Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/UserInterfaceState.xcuserstate


+ 288 - 16
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -910,22 +910,6 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Sources/Swifter/HttpRequest.swift"
-            timestampString = "471608201.621684"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "24"
-            endingLineNumber = "24"
-            landmarkName = "parseForm()"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
@@ -1006,5 +990,293 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471703999.441988"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "20"
+            endingLineNumber = "20"
+            landmarkName = "parseUrlencodedForm()"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999560.104501"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "43"
+            endingLineNumber = "43"
+            landmarkName = "parseMultiPartFormData()"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999560.104501"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "64"
+            endingLineNumber = "64"
+            landmarkName = "parseMultiPartFormData(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999560.104501"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "74"
+            endingLineNumber = "74"
+            landmarkName = "nextMultiPart(_:boundary:isFirst:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999560.104501"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "94"
+            endingLineNumber = "94"
+            landmarkName = "nextMultiPartLine(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999560.104501"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "103"
+            endingLineNumber = "103"
+            landmarkName = "nextMultiPartLine(_:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999553.189885"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "73"
+            endingLineNumber = "73"
+            landmarkName = "nextMultiPart(_:boundary:isFirst:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "471999618.576419"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "107"
+            endingLineNumber = "107"
+            landmarkName = "nextMultiPartBody(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "472000979.164829"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "121"
+            endingLineNumber = "121"
+            landmarkName = "nextMultiPartBody(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "472000979.164829"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "115"
+            endingLineNumber = "115"
+            landmarkName = "nextMultiPartBody(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/DemoServer.swift"
+            timestampString = "472000698.250372"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "56"
+            endingLineNumber = "56"
+            landmarkName = "demoServer(_:)"
+            landmarkType = "7">
+            <Locations>
+               <Location
+                  shouldBeEnabled = "No"
+                  ignoreCount = "0"
+                  continueAfterRunningActions = "No"
+                  symbolName = "SwifterSampleOSX.(demoServer (Swift.Optional&lt;Swift.String&gt;) -&gt; SwifterSampleOSX.HttpServer).(closure #4)"
+                  moduleName = "SwifterSampleOSX"
+                  usesParentBreakpointCondition = "Yes"
+                  urlString = "file:///Users/damiankolakowski/Desktop/swifter/Sources/Swifter/DemoServer.swift"
+                  timestampString = "472001463.193169"
+                  startingColumnNumber = "9223372036854775807"
+                  endingColumnNumber = "9223372036854775807"
+                  startingLineNumber = "56"
+                  endingLineNumber = "56"
+                  offsetFromSymbolStart = "2816">
+               </Location>
+               <Location
+                  shouldBeEnabled = "No"
+                  ignoreCount = "0"
+                  continueAfterRunningActions = "No"
+                  symbolName = "SwifterSampleOSX.(demoServer (Swift.Optional&lt;Swift.String&gt;) -&gt; SwifterSampleOSX.HttpServer).(closure #4).(closure #1)"
+                  moduleName = "SwifterSampleOSX"
+                  usesParentBreakpointCondition = "Yes"
+                  urlString = "file:///Users/damiankolakowski/Desktop/swifter/Sources/Swifter/DemoServer.swift"
+                  timestampString = "472001463.193401"
+                  startingColumnNumber = "9223372036854775807"
+                  endingColumnNumber = "9223372036854775807"
+                  startingLineNumber = "56"
+                  endingLineNumber = "56"
+                  offsetFromSymbolStart = "16">
+               </Location>
+            </Locations>
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "472000974.81569"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "114"
+            endingLineNumber = "114"
+            landmarkName = "nextMultiPartBody(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpRequest.swift"
+            timestampString = "472001106.790404"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "111"
+            endingLineNumber = "111"
+            landmarkName = "nextMultiPartBody(_:boundary:)"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/String+Misc.swift"
+            timestampString = "472001360.18561"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "91"
+            endingLineNumber = "91"
+            landmarkName = "UInt8ArrayToUTF8String(_:)"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/String+Misc.swift"
+            timestampString = "472001361.449268"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "93"
+            endingLineNumber = "93"
+            landmarkName = "UInt8ArrayToUTF8String(_:)"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "Sources/Swifter/HttpResponse.swift"
+            timestampString = "472002427.39139"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "81"
+            endingLineNumber = "81"
+            landmarkName = "data()"
+            landmarkType = "5">
+         </BreakpointContent>
+      </BreakpointProxy>
    </Breakpoints>
 </Bucket>

+ 6 - 13
SwifterSampleOSX/main.swift

@@ -6,24 +6,17 @@
 
 import Foundation
 
-class SwiftyJSONSerializer: Serializer {
-    func serialize(object: Any) throws -> String {
-        guard let obj = object as? JSON,
-            let rawString = obj.rawString() else {
-                throw SerializationError.InvalidObject
-        }
-        return rawString
-    }
-}
-
-
 let server = demoServer(NSBundle.mainBundle().resourcePath!)
 
 do {
     server["/SwiftyJSON"] = { request in
-        let serialize = SwiftyJSONSerializer()
         let js: JSON = ["return": "OK", "isItAJSON": true, "code" : 200]
-        return .OK(.Custom(serialize, js))
+        return .OK(.Custom(js, { object in
+            guard let obj = object as? JSON, let rawString = obj.rawString() else {
+                throw SerializationError.InvalidObject
+            }
+            return rawString
+        }))
     }
     server["/testAfterBaseRoute"] = { request in
         return .OK(.Html("ok !"))