Przeglądaj źródła

Added more values for Response enumeration.
Methods from Socket struct returns errors via NSErrorPointer class.
Methods from HttpParser class returns errors via NSErrorPointer class.
HttpServer::routes returns all urls handled by the server.

Damian Kołakowski 12 lat temu
rodzic
commit
76b68bda83

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


+ 4 - 6
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -10,11 +10,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Swifter/Socket.swift"
-            timestampString = "424026137.840619"
+            timestampString = "424730662.541514"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "60"
-            endingLineNumber = "60"
+            startingLineNumber = "79"
+            endingLineNumber = "79"
             landmarkName = "nosigpipe(_:)"
             landmarkType = "5">
          </BreakpointContent>
@@ -30,9 +30,7 @@
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
             startingLineNumber = "20"
-            endingLineNumber = "20"
-            landmarkName = "application(_:didFinishLaunchingWithOptions:)"
-            landmarkType = "5">
+            endingLineNumber = "20">
          </BreakpointContent>
       </BreakpointProxy>
    </Breakpoints>

+ 14 - 6
Swifter/AppDelegate.swift

@@ -20,11 +20,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         server["/"] = {
             return .OK("<html><body>Hello Swift</body></html>")
         }
-        
-        server["/hello"] = {
-            return .OK("<html><body>Hello !</body></html>")
+        server["/redirect"] = {
+            return .MovedPermanently("http://www.google.com")
         }
-        
         server["/long"] = {
             var longResponse = ""
             for k in 0..1000 {
@@ -32,7 +30,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             }
             return .OK(longResponse)
         }
-        
+        server["/routes"] = {
+            var listPage = "<html><body>Available services:<br><ul>"
+            for item in self.server.routes() {
+                listPage += "<li><a href=\"\(item)\">\(item)</a></li>"
+            }
+            listPage += "</ul></body></html>"
+            return .OK(listPage)
+        }
         server["/demo"] = {
             let demoPage =
                 "<html><body><center><h2>Hello Swift</h2>" +
@@ -42,7 +47,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             return .OK(demoPage)
         }
         
-        let (result, error) = server.start(8080)
+        var error: NSError?
+        if !server.start(error: &error) {
+            NSLog("Server start error: \(error)")
+        }
         
         return true
     }

+ 4 - 3
Swifter/Base.lproj/Main.storyboard

@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6162" systemVersion="14A238h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6154.17" systemVersion="13D65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6160"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6153.11"/>
     </dependencies>
     <scenes>
         <!--View Controller-->
         <scene sceneID="tne-QT-ifu">
             <objects>
-                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Swifter" customModuleProvider="target" sceneMemberID="viewController">
                     <layoutGuides>
                         <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                         <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
@@ -16,6 +16,7 @@
                         <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                        <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
                     </view>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>

+ 17 - 12
Swifter/HttpParser.swift

@@ -7,26 +7,30 @@
 
 import Foundation
 
-/* HTTP stream parser */
-
 class HttpParser {
     
-    func parseHttpHeader(socket: CInt) -> (String, Dictionary<String, String>)? {
-        if let statusLine = parseLine(socket) {
+    class func err(reason:String) -> NSError {
+        return NSError.errorWithDomain("HttpParser", code: 0, userInfo:[NSLocalizedFailureReasonErrorKey : reason])
+    }
+    
+    func nextHttpRequest(socket: CInt, error:NSErrorPointer = nil) -> (String, Dictionary<String, String>)? {
+        if let statusLine = nextLine(socket, error: error) {
             let statusTokens = split(statusLine, { $0 == " " })
-            if ( statusTokens.count >= 3 ) {
-                let path = statusTokens[1]
-                if let headers = parseHeaders(socket) {
-                    return (path, headers)
-                }
+            if ( statusTokens.count < 3 ) {
+                if error { error.memory = HttpParser.err("Invalid status line: \(statusLine)") }
+                return nil
+            }
+            let path = statusTokens[1]
+            if let headers = nextHeaders(socket, error: error) {
+                return (path, headers)
             }
         }
         return nil
     }
     
-    func parseHeaders(socket: CInt) -> Dictionary<String, String>? {
+    func nextHeaders(socket: CInt, error:NSErrorPointer) -> Dictionary<String, String>? {
         var headers = Dictionary<String, String>()
-        while let headerLine = parseLine(socket) {
+        while let headerLine = nextLine(socket, error: error) {
             if ( headerLine.isEmpty ) {
                 return headers
             }
@@ -45,7 +49,7 @@ class HttpParser {
         return nil
     }
     
-    func parseLine(socket: CInt) -> String? {
+    func nextLine(socket: CInt, error:NSErrorPointer) -> String? {
         // 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 = ""
@@ -57,6 +61,7 @@ class HttpParser {
             }
         } while ( n > 0 && buff[0] != 10 /* NL */ )
         if ( n == -1 ) {
+            if error { error.memory = Socket.socketRecentError("recv(...) failed.") }
             return nil
         }
         return characters

+ 96 - 64
Swifter/HttpServer.swift

@@ -7,34 +7,60 @@
 
 import Foundation
 
-enum ResponseStatus {
-    case OK(String)
-    case NotFound
+enum Response {
+    
+    case OK(String), Created, Accepted
+    case MovedPermanently(String)
+    case BadRequest, Unauthorized, Forbidden, NotFound
+    case InternalServerError
 
-    func numericValue() -> Int {
+    func statusCode() -> Int {
         switch self {
-            case .OK(_):
-                return 200
-            case .NotFound:
-                return 404
+        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
         }
     }
 
-    func textValue() -> String {
+    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"
+        }
+    }
+    
+    func headers() -> Dictionary<String, String> {
+        switch self {
+        case .MovedPermanently(let location) : return [ "Location" : location ]
+        default: return Dictionary()
+        }
+    }
+    
+    func body() -> String? {
         switch self {
-            case .OK(let text):
-                return text
-            case .NotFound:
-                return "Not found"
+            case .OK(let text)  : return text
+            default             : return nil
         }
     }
 }
 
-typealias Handler = Void -> ResponseStatus
-
-/* HTTP server */
 class HttpServer
 {
+    typealias Handler = Void -> Response
+    
     var handlers = Dictionary<String, Handler>()
     var acceptSocket: CInt = -1
     
@@ -47,57 +73,63 @@ class HttpServer
         }
     }
     
-    func start(listenPort: in_port_t) -> (Bool, String?) {
-        releaseAcceptSocket()
-        let (socket, error) = Socket.tcpForListen(listenPort)
-        if ( socket == -1 ) {
-            return (false, error)
+    func routes() -> Array<String> {
+        var results = Array<String>()
+        for (key,_) in handlers {
+            results.append(key)
         }
-        acceptSocket = socket
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
-            while ( self.acceptSocket != -1 ) {
-                var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), len: socklen_t = 0
-                let socket = accept(self.acceptSocket, &addr, &len)
-                if ( socket == -1 ) {
-                    self.releaseAcceptSocket();
-                    return
-                }
-                Socket.nosigpipe(socket)
-                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
-                    let parser = HttpParser()
-                    while let (path, headers) = parser.parseHttpHeader(socket) {
-                        let keepAlive = parser.supportsKeepAlive(headers)
-
-                        if let handler = self.handlers[path] {
-                            let responseStatus = handler()
-                            let responseText = responseStatus.textValue()
-                            let nsdata =
-                                responseText
-                                    .bridgeToObjectiveC()
-                                    .dataUsingEncoding(NSUTF8StringEncoding)
-
-                            Socket.writeStringUTF8(socket, string: "HTTP/1.1 \(responseStatus.numericValue())\r\n")
-                            Socket.writeStringUTF8(socket, string: "Content-Length: \(nsdata.length)\r\n")
-                            if keepAlive {
-                                Socket.writeStringUTF8(socket, string: "Connection: keep-alive\r\n")
-                            }
-                            Socket.writeStringUTF8(socket, string: "\r\n")
-                            Socket.writeStringUTF8(socket, string: responseText)
-                        } else {
-                            Socket.writeStringUTF8(socket, string: "HTTP/1.1 \(ResponseStatus.NotFound.numericValue())\r\n")
-                            Socket.writeStringUTF8(socket, string: "Content-Length: 0\r\n")
-                            if keepAlive {
-                                Socket.writeStringUTF8(socket, string: "Connection: keep-alive\r\n")
+        return results
+    }
+    
+    func start(listenPort: in_port_t = 8080, error:NSErrorPointer = nil) -> Bool {
+        releaseAcceptSocket()
+        if let socket = Socket.tcpForListen(port: listenPort, error: error) {
+            acceptSocket = socket
+            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
+                while ( self.acceptSocket != -1 ) {
+                    if let socket = Socket.acceptClientSocket(self.acceptSocket) {
+                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
+                            let parser = HttpParser()
+                            while let (path, headers) = parser.nextHttpRequest(socket) {
+                                let keepAlive = parser.supportsKeepAlive(headers)
+                                if let handler = self.handlers[path] {
+                                    HttpServer.writeResponse(socket, response: handler(), keepAlive: keepAlive)
+                                } else {
+                                    HttpServer.writeResponse(socket, response: Response.NotFound, keepAlive: keepAlive)
+                                }
+                                if !keepAlive { break }
                             }
-                            Socket.writeStringUTF8(socket, string: "\r\n")
-                        }
-                        if !keepAlive { break }
+                            Socket.release(socket)
+                        });
+                    } else {
+                        self.releaseAcceptSocket()
                     }
-                    Socket.release(socket)
-                });
-            }
-        });
-        return (true, nil)
+                }
+            });
+            return true
+        }
+        return false
+    }
+    
+    class func writeResponse(socket: CInt, response: Response, keepAlive: Bool) {
+        Socket.writeStringUTF8(socket, string: "HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
+        let messageBody = response.body()
+        if let body = messageBody {
+            let nsdata = body.bridgeToObjectiveC().dataUsingEncoding(NSUTF8StringEncoding)
+            Socket.writeStringUTF8(socket, string: "Content-Length: \(nsdata.length)\r\n")
+        } else {
+            Socket.writeStringUTF8(socket, string: "Content-Length: 0\r\n")
+        }
+        if keepAlive {
+            Socket.writeStringUTF8(socket, string: "Connection: keep-alive\r\n")
+        }
+        for (name, value) in response.headers() {
+            Socket.writeStringUTF8(socket, string: "\(name): \(value)\r\n")
+        }
+        Socket.writeStringUTF8(socket, string: "\r\n")
+        if let body = messageBody {
+            Socket.writeStringUTF8(socket, string: body)
+        }
     }
     
     func stop() {

+ 31 - 12
Swifter/Socket.swift

@@ -11,16 +11,23 @@ import Foundation
 
 struct Socket {
     
-    static func tcpForListen(port: in_port_t) -> (CInt, String?) {
+    static func socketRecentError(reason:String) -> NSError {
+        let code = errno
+        return NSError.errorWithDomain("SOCKET", code: Int(code), userInfo:
+            [NSLocalizedFailureReasonErrorKey : reason, NSLocalizedDescriptionKey : String.fromCString(strerror(code))])
+    }
+    
+    static func tcpForListen(port: in_port_t = 8080, error:NSErrorPointer = nil) -> CInt? {
         let s = socket(AF_INET, SOCK_STREAM, 0)
         if ( s == -1 ) {
-            return (-1, "socket() failed \(errno) - \(strerror(errno))")
+            if error { error.memory = socketRecentError("socket(...) failed.") }
+            return nil
         }
         var value: Int32 = 1;
         if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 ) {
-            let error = "setsockopt(...) failed \(errno) - \(strerror(errno))"
             release(s)
-            return (-1, error)
+            if error { error.memory = socketRecentError("setsockopt(...) failed.") }
+            return nil
         }
         nosigpipe(s)
         // Can't find htonl(...) function in Swift runtime so port value will be diffrent.
@@ -30,30 +37,42 @@ struct Socket {
         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))"
             release(s)
-            return (-1, error)
+            if error { error.memory = socketRecentError("bind(...) failed.") }
+            return nil
         }
         if ( listen(s, 20 /* max pending connection */ ) == -1 ) {
-            let error = "listen(...) failed \(errno) - \(strerror(errno))"
             release(s)
-            return (-1, error)
+            if error { error.memory = socketRecentError("listen(...) failed.") }
+            return nil
         }
-        return (s, nil)
+        return s
     }
     
-    static func writeStringUTF8(socket: CInt, string: String) {
+    static func writeStringUTF8(socket: CInt, string: String, error:NSErrorPointer = nil) -> Bool {
         var sent = 0;
         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))
             if ( s <= 0 ) {
-                return
+                if error { error.memory = socketRecentError("write(...) failed.") }
+                return false
             }
             sent += s
         }
-        return
+        return true
+    }
+    
+    static func acceptClientSocket(socket: CInt, error:NSErrorPointer = nil) -> CInt? {
+        var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), len: socklen_t = 0
+        let clientSocket = accept(socket, &addr, &len)
+        if ( clientSocket != -1 ) {
+            Socket.nosigpipe(clientSocket)
+            return clientSocket
+        }
+        if error { error.memory = socketRecentError("accept(...) failed.") }
+        return nil
     }
     
     static func nosigpipe(socket: CInt) {

BIN
github_code.png