Project Perfect让Swift在服务器端跑起来-让Perfect更Rails (五)

编者语:努力会有回报,加油吧!

       关于Perfect,已经从开发工具,原理,运行环境做了介绍。今天开始进入架构。其实,Perfect更像Java Servlet,我很喜欢Rails这种方式去构建。说句真心话,对于我这种.NET程序员,更希望只是换种语言,毕竟现在ASP.NET Core 很Cool。好!让大家看看我对Perfect的改造。

       再说说Perfect的运行原理。其实当用户发送请求时,都是首先找到PerfectServerModuleInit()这个方法,根据指定规则去找对应的Handlers,之后通过Handlers的方法handleRequest去处理相对应的事务处理。我们把这个流程用图的方式表示一下。
       
       其实handleRequest很接近我们的Controller,如果做成一个类似Rails的框架不是不可能的。这里我参考了在Github上的一个项目(https://github.com/groovelab/SwiftBBS)大家也可以去看看。

       首先我要扩展一下PerfectLib中的WebRequest和WebResponse这两个方法,针对WebRequest增加了action和参数,由于用到Rails思想,所以action是不能缺少的,后面的参数也是。而WebRepsonse把页面渲染和数据JSON输出做成统一的方法。这样做的好处就是减少了每个Handler一堆重复的工作.对应的文件是extension.swift

//
//  extension.swift
//  MVCDemo
//
//  Created by 卢建晖 on 16/2/27.
//  Copyright © 2016年 Kinfey. All rights reserved.
//

import PerfectLib

extension WebRequest {
    var action: String {
        return urlVariables["action"] ?? "index"
    }
    var acceptJson: Bool {
        return httpAccept().contains("application/json")
    }
}

extension WebResponse {
    func render(templatePath: String, values: MustacheEvaluationContext.MapType) throws -> String {
        let fullPath =  templatePath
        let file = File(fullPath)
        
        try file.openRead()
        defer { file.close() }
        let bytes = try file.readSomeBytes(file.size())
        
        let parser = MustacheParser()
        let str = UTF8Encoding.encode(bytes)
        let template = try parser.parse(str)
        
        let context = MustacheEvaluationContext(map: values)
        context.filePath = file.path()
        let collector = MustacheEvaluationOutputCollector()
        try template.evaluatePragmas(context, collector: collector, requireHandler: false)
        template.evaluate(context, collector: collector)
        return collector.asString()
    }
    
    func renderHTML(templatePath: String, values: MustacheEvaluationContext.MapType) throws {
        let responsBody = try render(templatePath, values: values)
        appendBodyString(responsBody)
        addHeader("Content-type", value: "text/html")
    }
    
    func outputJson(values: [String:JSONValue]) throws {
        addHeader("content-type", value: "application/json")
        let encoded = try values.jsonEncodedString()
        appendBodyString(encoded)
    }
}
       接下来我们做一个Controller.swift的基类,这个基类继承自RequesHandler包括了每个action所返回的结果。我这里参照.NET Core 把返回结果封装成IActionResult.并把handlerRequest做成一个统一处理的方法。
//
//  Controller.swift
//  MVCDemo
//
//  Created by 卢建晖 on 16/2/26.
//  Copyright © 2016年 Kinfey. All rights reserved.
//

import PerfectLib

class Controller : RequestHandler{
    
    
    enum IActionResult {
        case View(templatePath: String?, values: [String: Any])
        case Redirect(url: String)
        case Error(status: Int, message: String)
    }
    
    
    var request: WebRequest!
    var response: WebResponse!
    
    
    func handleRequest(request: WebRequest, response: WebResponse) {
        self.request = request
        self.response = response
        
        
        
        defer {
            response.requestCompletedCallback()
        }
        
        do{
        
        switch try Action(request.action) {
        case let .View(templatePath, responseValues):
            let values = responseValues
            if request.acceptJson {
                try response.outputJson(values)
            } else if let templatePath = templatePath {
                try response.renderHTML(templatePath, values: values)
            }
        case let .Redirect(url):
            response.redirectTo(url)
        case let .Error(status, message):
            response.setStatus(status, message: message)
            break;
        }
        }catch let e {
            print(e)
        }
    }
    
    func Action(action: String) throws -> IActionResult {
        return .Error(status: 500, message: "need implement")
    }
    
}
       为何要这样做,这里有一个方法Action,根据URL Routing去找到对应的Action方法,之后通过handlerRequest处理返回对应的页面或者JSON数据,我们做一个HomeController.swift看看。
import PerfectLib

class HomeController: Controller {
    
    override func Action(action: String) throws -> IActionResult {
        
        switch request.action {
        case "about" :
            return try About()
        default:
            return try Index()
        }
        
    }
    
    func Index() throws -> IActionResult{
        
        var values = [String: Any]()
        
        values["str"]="Hello Swift MVC Framework"
        
        return .View(templatePath: "Index.mustache", values: values)
    }
    
    func About() throws -> IActionResult{
        
        var values = [String: Any]()
        
        values["str"]="Hello Swift MVC Framework"
        
        return .View(templatePath: "About.mustache", values: values)
    }
    
    
}
      这里就是我们改造后的HomeController,而对应的URL Routing我参照.NET Core的方式放在Startup.swift上
import PerfectLib

public func PerfectServerModuleInit() {
    
    Routing.Handler.registerGlobally();
    Routing.Routes["GET", ["/","/Home/{action}"] ] = { _ in return HomeController() }
    
    
}
      最后我们把页面加上 index.mustache
<!DOCTYPE html>
<html lang="en">
<head>
	<title>Swift MVC</title>
</head>
<body>
<h1>{{str}}</h1>
</body>
</html>

     about.mustache

<!DOCTYPE html>
<html lang="en">
<head>
<title>About</title>
</head>
<body>
<h1>This is Kinfey design</h1>
</body>
</html>
    基本上就可以完成我们的Rails改造了,看看在Xcode的结构,很Rails,很.NET Core吧
    
    看看运行的过程,如图
    
    我们运行下
        

    
    这里补充一点,如果你要把页面模版在Xcode中使用必须要对Build Phase进行设置,在Copy Files中添加,需要设置Desination为Product Directory,如图
    
     今天说到这里,祝周末愉快!


       
       

猜你喜欢

转载自blog.csdn.net/u014388424/article/details/50754770