Przeglądaj źródła

Support for keep-alive like communication.
Added enumeration for common HTTP status codes.
HTTP 404 NOT FOUND is returned when there is not handler for a request.

Damian Kołakowski 12 lat temu
rodzic
commit
e453ee6f44

+ 2 - 0
Swifter.xcodeproj/project.pbxproj

@@ -411,6 +411,7 @@
 				7C839B8C19422D00003A6950 /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
 		};
 		7C839B8D19422D00003A6950 /* Build configuration list for PBXNativeTarget "SwifterTests" */ = {
 			isa = XCConfigurationList;
@@ -419,6 +420,7 @@
 				7C839B8F19422D00003A6950 /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
 	};

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


+ 11 - 11
Swifter/AppDelegate.swift

@@ -17,26 +17,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     
     func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
         
-        server["/"] = { () -> (CInt, String) in
-            return (200, "<html><body>Hello Swift</body></html>")
+        server["/"] = { () -> (Int, String) in
+            return (HttpServer.Statuses.OK, "<html><body>Hello Swift</body></html>")
         }
         
-        server["/hello"] = { () -> (CInt, String) in
-            return (200, "<html><body>Hello !</body></html>")
+        server["/hello"] = { () -> (Int, String) in
+            return (HttpServer.Statuses.OK, "<html><body>Hello !</body></html>")
         }
         
-        server["/long"] = { () -> (CInt, String) in
+        server["/long"] = { () -> (Int, String) in
             var longResponse = ""
-            for k in 0..100000 {
-                longResponse += "(\(k)),"
+            for k in 0..1000 {
+                longResponse += "(\(k)),->"
             }
-            return (200, longResponse)
+            return (HttpServer.Statuses.OK, longResponse)
         }
         
-        server["/demo"] = { () -> (CInt, String) in
-            return (200, "<html><body><center><h2>Hello Swift</h2>" +
+        server["/demo"] = { () -> (Int, String) in
+            return (HttpServer.Statuses.OK, "<html><body><center><h2>Hello Swift</h2>" +
                 "<img src=\"https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png\"/><br>" +
-                            "<h4>\(UIDevice().name), \(UIDevice().systemVersion)</h4></center></body></html>")
+                            "<h4>\(UIDevice().name), \(UIDevice().systemVersion)</h4></center><iframe src=\"/demo2\"></iframe><iframe src=\"/hello\"></iframe></body></html>")
         }
         
         let (result, error) = server.start(8080)

+ 12 - 3
Swifter/HttpParser.swift

@@ -32,7 +32,10 @@ class HttpParser {
             }
             let headerTokens = split(headerLine, { $0 == ":" })
             if ( headerTokens.count >= 2 ) {
-                let headerName = headerTokens[0]
+                // 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 can keep lower case version.
+                let headerName = headerTokens[0].lowercaseString
                 let headerValue = headerTokens[1]
                 if ( !headerName.isEmpty && !headerValue.isEmpty ) {
                     headers.updateValue(headerValue, forKey: headerName)
@@ -43,7 +46,7 @@ class HttpParser {
     }
     
     func parseLine(socket: CInt) -> String? {
-        // TODO - read more bytes than one
+        // TODO - read more bytes than one. It makes the server very slow.
         // TODO - check if there is a nicer way to manipulate bytes with Swift ( recv(...) -> String )
         var characters: String = ""
         var buff: UInt8[] = UInt8[](count: 1, repeatedValue: 0), n: Int = 1
@@ -56,7 +59,13 @@ class HttpParser {
         if ( n == -1 ) {
             return nil
         }
-        println("SOCKET LOG [\(socket)] -> \(characters)")
         return characters
     }
+    
+    func supportsKeepAlive(headers: Dictionary<String, String>) -> Bool {
+        if let value = headers["connection"] {
+            return "keep-alive" == value.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).lowercaseString
+        }
+        return false
+    }
 }

+ 26 - 5
Swifter/HttpServer.swift

@@ -11,10 +11,15 @@ import Foundation
 
 class HttpServer
 {
-    var handlers: Dictionary<String, (Void -> (CInt, String))> = Dictionary()
+    enum Statuses {
+        static let OK = 200
+        static let NOT_FOUND = 404
+    }
+    
+    var handlers: Dictionary<String, (Void -> (Int, String))> = Dictionary()
     var acceptSocket: CInt = -1
     
-    subscript (path: String) -> ((Void -> (CInt, String))) {
+    subscript (path: String) -> ((Void -> (Int, String))) {
         get {
             return handlers[path]!
         }
@@ -41,11 +46,27 @@ class HttpServer
                 Socket.nosigpipe(socket)
                 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
                     let parser = HttpParser()
-                    if let (path, headers) = parser.parseHttpHeader(socket) {
+                    while let (path, headers) = parser.parseHttpHeader(socket) {
                         if let handler = self.handlers[path] {
                             let (status, response) = handler()
-                            // no support for keep-alive for now so let's stay with HTTP 1.0
-                            Socket.writeString(socket, response: "HTTP/1.0 \(status)\r\n\r\n\(response)")
+                            Socket.writeStringUTF8(socket, string: "HTTP/1.1 \(status)\r\n")
+                            let nsdata = response.bridgeToObjectiveC().dataUsingEncoding(NSUTF8StringEncoding)
+                            Socket.writeStringUTF8(socket, string: "Content-Length: \(nsdata.length)\r\n")
+                            if parser.supportsKeepAlive(headers) {
+                                Socket.writeStringUTF8(socket, string: "Connection: keep-alive\r\n")
+                            }
+                            Socket.writeStringUTF8(socket, string: "\r\n")
+                            Socket.writeStringUTF8(socket, string: response)
+                        } else {
+                            Socket.writeStringUTF8(socket, string: "HTTP/1.1 \(Statuses.NOT_FOUND)\r\n")
+                            Socket.writeStringUTF8(socket, string: "Content-Length: 0\r\n")
+                            if parser.supportsKeepAlive(headers) {
+                                Socket.writeStringUTF8(socket, string: "Connection: keep-alive\r\n")
+                            }
+                            Socket.writeStringUTF8(socket, string: "\r\n")
+                        }
+                        if !parser.supportsKeepAlive(headers) {
+                            break
                         }
                     }
                     Socket.release(socket)

+ 9 - 0
Swifter/MyPlayground.playground/timeline.xctimeline

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Timeline
+   version = "3.0">
+   <TimelineItems>
+      <LoggerValueHistoryTimelineItem
+         documentLocation = "#CharacterRangeLen=9&amp;CharacterRangeLoc=117&amp;EndingColumnNumber=41&amp;EndingLineNumber=6&amp;StartingColumnNumber=32&amp;StartingLineNumber=6&amp;Timestamp=424200144.512699">
+      </LoggerValueHistoryTimelineItem>
+   </TimelineItems>
+</Timeline>

+ 6 - 4
Swifter/Socket.swift

@@ -23,10 +23,11 @@ struct Socket {
             return (-1, error)
         }
         nosigpipe(s)
-        var addr: sockaddr_in = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET),
+        // Can't find htonl(...) function in Swift runtime so port value will be diffrent.
+        var addr = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET),
             sin_port: port, sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
         
-        var sock_addr: sockaddr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+        var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
         memcpy(&sock_addr, &addr, UInt(sizeof(sockaddr_in)))
         if ( bind(s, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 ) {
             let error = "bind(...) failed \(errno) - \(strerror(errno))"
@@ -41,9 +42,9 @@ struct Socket {
         return (s, nil)
     }
     
-    static func writeString(socket: CInt, response: String) {
+    static func writeStringUTF8(socket: CInt, string: String) {
         var sent = 0;
-        let nsdata = response.bridgeToObjectiveC().dataUsingEncoding(NSUTF8StringEncoding)
+        let nsdata = string.bridgeToObjectiveC().dataUsingEncoding(NSUTF8StringEncoding)
         let unsafePointer = UnsafePointer<UInt8>(nsdata.bytes)
         while ( sent < nsdata.length ) {
             let s = write(socket, unsafePointer + sent, UInt(nsdata.length - sent))
@@ -52,6 +53,7 @@ struct Socket {
             }
             sent += s
         }
+        return
     }
     
     static func nosigpipe(socket: CInt) {