微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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,如图
今天说到这里,祝周末愉快!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐