0001    //
0002    //  WebApps.swift
0003    //  Dynamo
0004    //
0005    //  Created by John Holdsworth on 20/06/2015.
0006    //  Copyright (c) 2015 John Holdsworth. All rights reserved.
0007    //
0008    //  $Id: //depot/Dynamo/Sources/Swiftlets.swift#8 $
0009    //
0010    //  Repo: https://github.com/johnno1962/Dynamo
0011    //
0012    
0013    import Foundation
0014    
0015    #if os(Linux)
0016    import NSLinux
0017    #endif
0018    
0019    // MARK: Swiftlets for dynamic content
0020    
0021    /**
0022        Base application swiftlet testing to see if the URL path matches against a prefix to decide if it should
0023        process it. Handles parsing of HTTP headers to present to web application code.
0024     */
0025    
0026    public class ApplicationSwiftlet
Generated.swift:32
public class HTMLApplicationSwiftlet: ApplicationSwiftlet {
Swiftlets.swift:143
public class SessionSwiftlet: ApplicationSwiftlet {
Swiftlets.swift:146
    var sessions = [String:ApplicationSwiftlet]()
Swiftlets.swift:345
public class ServerPagesSwiftlet: ApplicationSwiftlet {
: _NSObject_, DynamoBrowserSwiftlet { 0027 0028 let pathPrefix
Swiftlets.swift:35
        self.pathPrefix = pathPrefix
Swiftlets.swift:40
        if let pathInfo = httpClient.url.path where pathInfo.hasPrefix( pathPrefix ) {
Swiftlets.swift:41
            let endIndex = pathInfo.rangeOfString( pathPrefix )!.endIndex
Swiftlets.swift:186
            out.setCookie( cookieName, value: sessionKey!, path: pathPrefix )
: String 0029 0030 /** 0031 Basic Application Swiftlets are identified by a prefix to their URL's path 0032 */ 0033 0034 public init
Swiftlets.swift:158
        super.init( pathPrefix: pathPrefix )
Swiftlets.swift:357
        super.init( pathPrefix: "/**.ssp" )
( pathPrefix: String ) { 0035 self.pathPrefix = pathPrefix 0036 } 0037 0038 public func present
Swiftlets.swift:396
                return reloader.present( httpClient )
( httpClient: DynamoHTTPConnection ) -> DynamoProcessed { 0039 0040 if let pathInfo = httpClient.url.path where pathInfo.hasPrefix( pathPrefix ) { 0041 let endIndex = pathInfo.rangeOfString( pathPrefix )!.endIndex 0042 return process( httpClient, pathInfo: pathInfo.substringToIndex( endIndex ) ) 0043 } 0044 0045 return .NotProcessed 0046 } 0047 0048 /** 0049 Filters by path prefix to determine if this Swiftlet is to be used and parses browser 0050 query string, any post data and cookeis arriving from the browser. 0051 */ 0052 0053 public func process
Swiftlets.swift:42
            return process( httpClient, pathInfo: pathInfo.substringToIndex( endIndex ) )
( httpClient: DynamoHTTPConnection, pathInfo: String ) -> DynamoProcessed { 0054 0055 var cookies = [String:String]() 0056 if let cookieHeader = httpClient.requestHeaders["Cookie"] { 0057 addParameters( &cookies, from: cookieHeader, delimeter: "; " ) 0058 } 0059 0060 var parameters = [String:String]() 0061 if let queryString = httpClient.url.query { 0062 addParameters( &parameters, from: queryString ) 0063 } 0064 0065 if httpClient.method == "POST" { 0066 if httpClient.contentType == "application/json" { 0067 #if !os(Linux) 0068 if let json = httpClient.postJSON() { 0069 processJSON( httpClient, 0070 pathInfo: pathInfo, 0071 parameters: parameters, 0072 cookies: cookies, 0073 json: json ) 0074 } 0075 #endif 0076 return httpClient.knowsResponseLength ? .ProcessedAndReusable : .Processed 0077 } 0078 0079 if httpClient.contentType == "application/x-www-form-urlencoded" { 0080 if let postString = httpClient.postString() { 0081 addParameters( &parameters, from: postString ) 0082 } 0083 else { 0084 dynamoLog( "POST data not available" ) 0085 } 0086 } 0087 } 0088 0089 processRequest( httpClient, pathInfo: pathInfo, parameters: parameters, cookies: cookies ) 0090 0091 return httpClient.knowsResponseLength ? .ProcessedAndReusable : .Processed 0092 } 0093 0094 private func addParameters
Swiftlets.swift:57
            addParameters( &cookies, from: cookieHeader, delimeter: "; " )
Swiftlets.swift:62
            addParameters( &parameters, from: queryString )
Swiftlets.swift:81
                    addParameters( &parameters, from: postString )
( inout parameters: [String:String], from queryString: String, delimeter: String = "&" ) { 0095 for nameValue in queryString.componentsSeparatedByString( delimeter ) { 0096 if let divider = nameValue.rangeOfString( "=" )?.startIndex { 0097 let value = nameValue.substringFromIndex( divider.advancedBy( 1 ) ) 0098 if let value = value 0099 .stringByReplacingOccurrencesOfString( "+", withString: " " ) 0100 .stringByRemovingPercentEncoding { 0101 parameters[nameValue.substringToIndex( divider )] = value 0102 } 0103 } 0104 else { 0105 parameters[nameValue] = "" 0106 } 0107 } 0108 } 0109 0110 /** 0111 An application Swiftlet implements this method to performs it's processing printing to the browser 0112 or setting a "response" as whole which will allow the connection to be reused. 0113 */ 0114 0115 public func processRequest
Swiftlets.swift:89
        processRequest( httpClient, pathInfo: pathInfo, parameters: parameters, cookies: cookies )
Swiftlets.swift:124
        processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies )
Swiftlets.swift:192
            sessionApp.processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies )
( out: DynamoHTTPConnection, pathInfo: String, parameters: [String:String], cookies: [String:String] ) { 0116 dynamoLog( "DynamoApplicationSwiftlet.processRequest(): Subclass responsibility" ) 0117 } 0118 0119 /** 0120 Sepcial treatment of JSON Post 0121 */ 0122 0123 public func processJSON
Swiftlets.swift:69
                    processJSON( httpClient,
( out: DynamoHTTPConnection, pathInfo: String, parameters: [String:String], cookies: [String:String], json: AnyObject ) { 0124 processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies ) 0125 } 0126 0127 } 0128 0129 // MARK: Session based applications 0130 0131 /** 0132 Default session expiry time in seconds 0133 */ 0134 0135 public var dynanmoDefaultSessionExpiry
Swiftlets.swift:226
        self.expiry = NSDate().timeIntervalSinceReferenceDate + dynanmoDefaultSessionExpiry
: NSTimeInterval = 15*60 0136 private var sessionExpiryCheckInterval
Swiftlets.swift:172
        let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(sessionExpiryCheckInterval * Double(NSEC_PER_SEC)))
: NSTimeInterval = 60 0137 0138 /** 0139 Swiftlet that creates now instancews of the application class per user session. 0140 Sessions are identified by a UUID in a "DynamoSession" Coookie. 0141 */ 0142 0143 public class SessionSwiftlet
Swiftlets.swift:208
    let manager: SessionSwiftlet
Swiftlets.swift:223
    required public init( manager: SessionSwiftlet, sessionKey: String ) {
Swiftlets.swift:249
public class BundleSwiftlet: SessionSwiftlet {
: ApplicationSwiftlet { 0144 0145 var appClass
Swiftlets.swift:156
        self.appClass = appClass
Swiftlets.swift:185
            sessions[sessionKey!] = appClass.init( manager: self, sessionKey: sessionKey! )
: SessionApplication.Type 0146 var sessions
Swiftlets.swift:163
        for (key, session) in sessions {
Swiftlets.swift:167
                    sessions.removeValueForKey( key )
Swiftlets.swift:183
        if sessionKey == nil || sessions[sessionKey!] == nil {
Swiftlets.swift:185
            sessions[sessionKey!] = appClass.init( manager: self, sessionKey: sessionKey! )
Swiftlets.swift:191
        if let sessionApp = sessions[sessionKey!] {
Swiftlets.swift:216
        manager.sessions.removeValueForKey( sessionKey )
= [String:ApplicationSwiftlet]() 0147 0148 private var sessionLock
Swiftlets.swift:166
                    sessionLock.lock()
Swiftlets.swift:168
                    sessionLock.unlock()
Swiftlets.swift:181
    	sessionLock.lock()
Swiftlets.swift:189
        sessionLock.unlock()
= NSLock() 0149 private let cookieName
Swiftlets.swift:157
        self.cookieName = cookieName
Swiftlets.swift:182
        var sessionKey = cookies[cookieName]
Swiftlets.swift:186
            out.setCookie( cookieName, value: sessionKey!, path: pathPrefix )
: String 0150 0151 /** 0152 Makea bindling between a pat pah prefix and a class the will be instantieted to process a session of requests 0153 */ 0154 0155 public init
Swiftlets.swift:281
                super.init( pathPrefix: pathPrefix, appClass: appClass )
Swiftlets.swift:290
        super.init( pathPrefix: pathPrefix, appClass: SessionApplication.self )
( pathPrefix: String, appClass: SessionApplication.Type, cookieName: String = "DynamoSession" ) { 0156 self.appClass = appClass 0157 self.cookieName = cookieName 0158 super.init( pathPrefix: pathPrefix ) 0159 cleanupSessions() 0160 } 0161 0162 private func cleanupSessions
Swiftlets.swift:159
        cleanupSessions()
Swiftlets.swift:173
        dispatch_after( delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), cleanupSessions )
() { 0163 for (key, session) in sessions { 0164 if let session = session as? SessionApplication 0165 where session.expiry < NSDate().timeIntervalSinceReferenceDate { 0166 sessionLock.lock() 0167 sessions.removeValueForKey( key ) 0168 sessionLock.unlock() 0169 } 0170 } 0171 0172 let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(sessionExpiryCheckInterval * Double(NSEC_PER_SEC))) 0173 dispatch_after( delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), cleanupSessions ) 0174 } 0175 /** 0176 Create a new instance of the application class to process the request if request and have it process it. 0177 */ 0178 0179 public override func processRequest
Swiftlets.swift:330
        super.processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies )
( out: DynamoHTTPConnection, pathInfo: String, parameters: [String : String], cookies: [String : String] ) { 0180 0181 sessionLock.lock() 0182 var sessionKey = cookies[cookieName] 0183 if sessionKey == nil || sessions[sessionKey!] == nil { 0184 sessionKey = NSUUID().UUIDString 0185 sessions[sessionKey!] = appClass.init( manager: self, sessionKey: sessionKey! ) 0186 out.setCookie( cookieName, value: sessionKey!, path: pathPrefix ) 0187 out.contentType = dynamoHtmlMimeType 0188 } 0189 sessionLock.unlock() 0190 0191 if let sessionApp = sessions[sessionKey!] { 0192 sessionApp.processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies ) 0193 } 0194 else { 0195 dynamoLog( "Missing app for session \(sessionKey)" ) 0196 } 0197 } 0198 0199 } 0200 0201 /** 0202 Class to be subclassed for the application code when writing session based applications 0203 */ 0204 0205 public class SessionApplication
Swiftlets.swift:145
    var appClass: SessionApplication.Type
Swiftlets.swift:155
    public init( pathPrefix: String, appClass: SessionApplication.Type, cookieName: String = "DynamoSession" ) {
Swiftlets.swift:164
            if let session = session as? SessionApplication
Swiftlets.swift:280
            if let appClass = bundle.classNamed( "\(bundleName)Swiftlet" ) as? SessionApplication.Type {
Swiftlets.swift:290
        super.init( pathPrefix: pathPrefix, appClass: SessionApplication.self )
: HTMLApplicationSwiftlet { 0206 0207 let sessionKey
Swiftlets.swift:216
        manager.sessions.removeValueForKey( sessionKey )
Swiftlets.swift:225
        self.sessionKey = sessionKey
: String 0208 let manager
Swiftlets.swift:216
        manager.sessions.removeValueForKey( sessionKey )
Swiftlets.swift:224
        self.manager = manager
: SessionSwiftlet 0209 var expiry
Swiftlets.swift:165
                where session.expiry < NSDate().timeIntervalSinceReferenceDate {
Swiftlets.swift:226
        self.expiry = NSDate().timeIntervalSinceReferenceDate + dynanmoDefaultSessionExpiry
: NSTimeInterval 0210 0211 /** 0212 Clear out the current session for the next request 0213 */ 0214 0215 public func clearSession() { 0216 manager.sessions.removeValueForKey( sessionKey ) 0217 } 0218 0219 /** 0220 Create new instance of applicatoin swiftlet on damand for session based processing 0221 */ 0222 0223 required public init
Swiftlets.swift:185
            sessions[sessionKey!] = appClass.init( manager: self, sessionKey: sessionKey! )
( manager: SessionSwiftlet, sessionKey: String ) { 0224 self.manager = manager 0225 self.sessionKey = sessionKey 0226 self.expiry = NSDate().timeIntervalSinceReferenceDate + dynanmoDefaultSessionExpiry 0227 super.init( pathPrefix: "N/A" ) 0228 } 0229 0230 /** 0231 Overridden by applpication code toprocess request. 0232 */ 0233 0234 public override func processRequest( out: DynamoHTTPConnection, pathInfo: String, parameters: [String : String], cookies: [String : String] ) { 0235 dynamoLog( "DynamoSessionApplication.processRequest(): Subclass responsibility" ) 0236 } 0237 0238 } 0239 0240 // MARK: Bundle based, reloading swiftlets 0241 0242 /** 0243 This Swiftlet is session based and also loads it's application code from a code bundle with a ".ssp" extension. 0244 If the module includes the Utilities/AutoLoader.m code it will reload and swizzle it'a new implementation when 0245 the bundle is rebuilt/re-deployed for hot-swapping in the code. Existing instances/sessions receive the new code 0246 but retain their state. This does not work for changes to the layout or number of properties in the class. 0247 */ 0248 0249 public class BundleSwiftlet
Swiftlets.swift:348
    var reloaders = [String:BundleSwiftlet]()
Swiftlets.swift:384
                    if let reloader = BundleSwiftlet( pathPrefix: sspPath,
: SessionSwiftlet { 0250 0251 let bundleName
Swiftlets.swift:274
        self.bundleName = bundleName
Swiftlets.swift:305
            let nextPath = "/tmp/\(bundleName)V\(loadNumber).ssp"
: String 0252 let bundlePath
Swiftlets.swift:275
        self.bundlePath = bundlePath
Swiftlets.swift:314
                try fileManager.copyItemAtPath( bundlePath, toPath: nextPath )
: String 0253 let binaryPath
Swiftlets.swift:276
        self.binaryPath = "\(bundlePath)/Contents/MacOS/\(bundleName)"
Swiftlets.swift:302
        if let attrs = try? fileManager.attributesOfItemAtPath( binaryPath),
: String 0254 var loaded
Swiftlets.swift:277
        self.loaded = NSDate().timeIntervalSinceReferenceDate
Swiftlets.swift:304
                where lastModified > loaded {
Swiftlets.swift:319
                        self.loaded = lastModified
: NSTimeInterval 0255 let fileManager
Swiftlets.swift:302
        if let attrs = try? fileManager.attributesOfItemAtPath( binaryPath),
Swiftlets.swift:309
                try fileManager.removeItemAtPath( nextPath )
Swiftlets.swift:314
                try fileManager.copyItemAtPath( bundlePath, toPath: nextPath )
= NSFileManager.defaultManager() 0256 var loadNumber
Swiftlets.swift:305
            let nextPath = "/tmp/\(bundleName)V\(loadNumber).ssp"
Swiftlets.swift:306
            loadNumber += 1
= 0 0257 0258 /** 0259 A convenience initialiser for ".ssp" bundles that are in the projects resources 0260 */ 0261 0262 public convenience init?( pathPrefix: String, bundleName: String ) { 0263 let bundlePath = NSBundle.mainBundle().pathForResource( bundleName, ofType: "ssp" )! 0264 self.init( pathPrefix: pathPrefix, bundleName: bundleName, bundlePath: bundlePath ) 0265 } 0266 0267 /** 0268 Initialises and performs an initial load of bundle specified. 0269 Bundle must contain a class with the @objc name "\(bundleNme)Swiftlet". 0270 */ 0271 0272 public init
Swiftlets.swift:264
        self.init( pathPrefix: pathPrefix, bundleName: bundleName, bundlePath: bundlePath )
Swiftlets.swift:384
                    if let reloader = BundleSwiftlet( pathPrefix: sspPath,
?( pathPrefix: String, bundleName: String, bundlePath: String ) { 0273 0274 self.bundleName = bundleName 0275 self.bundlePath = bundlePath 0276 self.binaryPath = "\(bundlePath)/Contents/MacOS/\(bundleName)" 0277 self.loaded = NSDate().timeIntervalSinceReferenceDate 0278 0279 if let bundle = NSBundle( path: bundlePath ) where bundle.load() { 0280 if let appClass = bundle.classNamed( "\(bundleName)Swiftlet" ) as? SessionApplication.Type { 0281 super.init( pathPrefix: pathPrefix, appClass: appClass ) 0282 return 0283 } 0284 else { 0285 dynamoLog( "Could not locate class with @objc name \(bundleName)Swiftlet in \(bundlePath)") 0286 } 0287 } 0288 0289 dynamoLog( "Could not find/load swiftlet for bundle \(bundlePath)" ) 0290 super.init( pathPrefix: pathPrefix, appClass: SessionApplication.self ) 0291 return nil 0292 } 0293 0294 /** 0295 When it comes to process a bundle swiftlet, the modificatoin timme of the binsry 0296 is checked and if the bundle has been changed will reload the bundle by copying 0297 it to a new unique path in /tmp. 0298 */ 0299 0300 public override func processRequest( out: DynamoHTTPConnection, pathInfo: String, parameters: [String : String], cookies: [String : String] ) { 0301 0302 if let attrs = try? fileManager.attributesOfItemAtPath( binaryPath), 0303 lastModified = (attrs[NSFileModificationDate] as? NSDate)?.timeIntervalSinceReferenceDate 0304 where lastModified > loaded { 0305 let nextPath = "/tmp/\(bundleName)V\(loadNumber).ssp" 0306 loadNumber += 1 0307 0308 do { 0309 try fileManager.removeItemAtPath( nextPath ) 0310 } catch _ { 0311 } 0312 0313 do { 0314 try fileManager.copyItemAtPath( bundlePath, toPath: nextPath ) 0315 0316 if let bundle = NSBundle( path: nextPath ) { 0317 if bundle.load() { 0318 // AutoLoader.m Swizzles new implementation 0319 self.loaded = lastModified 0320 } 0321 else { 0322 dynamoLog( "Could not reload bundle \(nextPath)" ) 0323 } 0324 } 0325 } catch let error as NSError { 0326 dynamoLog( "Could not copy bundle to \(nextPath) \(error)" ) 0327 } 0328 } 0329 0330 super.processRequest( out, pathInfo: pathInfo, parameters: parameters, cookies: cookies ) 0331 } 0332 0333 } 0334 0335 // MARK: Reloading swiftlet based in bundle inside documentRoot 0336 0337 /** 0338 A specialisation of a bundle reloading, session based swiftlet where the bundle is loaded 0339 from the web document directory. As before it reloads and hot-swaps in the new code if the 0340 bundle is updated. 0341 */ 0342 0343 #if !os(Linux) 0344 0345 public class ServerPagesSwiftlet: ApplicationSwiftlet { 0346 0347 let documentRoot
Swiftlets.swift:356
        self.documentRoot = documentRoot
Swiftlets.swift:372
            if sspPath != path && fileManager.fileExistsAtPath( "\(documentRoot)/\(host)\(path)") {
Swiftlets.swift:376
            let sspFullPath = "\(documentRoot)/\(host)\(sspPath)"
: String 0348 var reloaders
Swiftlets.swift:377
            let reloader = reloaders[sspPath]
Swiftlets.swift:386
                        reloaders[sspPath] = reloader
Swiftlets.swift:395
            if let reloader = reloaders[sspPath] {
= [String:BundleSwiftlet]() 0349 let fileManager
Swiftlets.swift:372
            if sspPath != path && fileManager.fileExistsAtPath( "\(documentRoot)/\(host)\(path)") {
Swiftlets.swift:379
            if reloader == nil && fileManager.fileExistsAtPath( sspFullPath ) {
= NSFileManager.defaultManager() 0350 0351 /** 0352 Document root indicates where the ".ssp" bundles are to be found 0353 */ 0354 0355 public init( documentRoot: String ) { 0356 self.documentRoot = documentRoot 0357 super.init( pathPrefix: "/**.ssp" ) 0358 } 0359 0360 /** 0361 Parses url to see if there is a path to a .ssp bundle in the document root. 0362 If present it will lbe loaded as a reloadable bnudle to process the request. 0363 */ 0364 0365 override public func present( httpClient: DynamoHTTPConnection ) -> DynamoProcessed { 0366 0367 let path = httpClient.path 0368 0369 if let sspMatch = path.rangeOfString( ".ssp" )?.endIndex, host = httpClient.requestHeaders["Host"] { 0370 let sspPath = path.substringToIndex( sspMatch ) 0371 0372 if sspPath != path && fileManager.fileExistsAtPath( "\(documentRoot)/\(host)\(path)") { 0373 return .NotProcessed 0374 } 0375 0376 let sspFullPath = "\(documentRoot)/\(host)\(sspPath)" 0377 let reloader = reloaders[sspPath] 0378 0379 if reloader == nil && fileManager.fileExistsAtPath( sspFullPath ) { 0380 if let nameStart = sspPath.rangeOfString( "/", 0381 options: NSStringCompareOptions.BackwardsSearch )?.endIndex { 0382 let nameEnd = sspPath.endIndex.advancedBy(-4 ) 0383 let bundleName = sspPath.substringWithRange( Range( start: nameStart, end: nameEnd ) ) 0384 if let reloader = BundleSwiftlet( pathPrefix: sspPath, 0385 bundleName: bundleName, bundlePath: sspFullPath ) { 0386 reloaders[sspPath] = reloader 0387 } 0388 } 0389 else { 0390 dynamoLog( "Unable to parse .ssp path: \(sspPath)" ) 0391 return .NotProcessed 0392 } 0393 } 0394 0395 if let reloader = reloaders[sspPath] { 0396 return reloader.present( httpClient ) 0397 } 0398 else { 0399 dynamoLog( "Missing .ssp bundle for path \(path)" ) 0400 } 0401 } 0402 0403 return .NotProcessed 0404 } 0405 } 0406 0407 #endif 0408