编者语:努力会有回报,加油吧!
关于Perfect,已经从开发工具,原理,运行环境做了介绍。今天开始进入架构。其实,Perfect更像JavaServlet,我很喜欢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
[plain]view plaincopy
print?
- //
 - //extension.swift
 - //MVCDemo
 - //
 - //Createdby卢建晖on16/2/27.
 - //copyright©2016年Kinfey.Allrightsreserved.
 - //
 - importPerfectLib
 - extensionWebRequest{
 - varaction:String{
 - returnurlvariables["action"]??"index"
 - }
 - varacceptJson:Bool{
 - returnhttpAccept().contains("application/json")
 - }
 - }
 - extensionWebResponse{
 - funcrender(templatePath:String,values:MustacheEvaluationContext.MapType)throws->String{
 - letfullPath=templatePath
 - letfile=File(fullPath)
 - tryfile.openRead()
 - defer{file.close()}
 - letbytes=tryfile.readSomeBytes(file.size())
 - letparser=MustacheParser()
 - letstr=UTF8Encoding.encode(bytes)
 - lettemplate=tryparser.parse(str)
 - letcontext=MustacheEvaluationContext(map:values)
 - context.filePath=file.path()
 - letcollector=MustacheEvaluationOutputCollector()
 - trytemplate.evaluatePragmas(context,collector:collector,requireHandler:false)
 - template.evaluate(context,collector:collector)
 - returncollector.asstring()
 - }
 - funcrenderHTML(templatePath:String,values:MustacheEvaluationContext.MapType)throws{
 - letresponsBody=tryrender(templatePath,values:values)
 - appendBodyString(responsBody)
 - addHeader("Content-type",value:"text/html")
 - }
 - funcoutputJson(values:[String:JSONValue])throws{
 - addHeader("content-type",value:"application/json")
 - letencoded=tryvalues.jsonEncodedString()
 - appendBodyString(encoded)
 - }
 - }
 
接下来我们做一个Controller.swift的基类,这个基类继承自RequesHandler包括了每个action所返回的结果。我这里参照.NET Core 把返回结果封装成IActionResult.并把handlerRequest做成一个统一处理的方法。
[plain]view plaincopy
print?
- //
 - //Controller.swift
 - //MVCDemo
 - //
 - //Createdby卢建晖on16/2/26.
 - //copyright©2016年Kinfey.Allrightsreserved.
 - //
 - importPerfectLib
 - classController:RequestHandler{
 - enumIActionResult{
 - caseView(templatePath:String?,values:[String:Any])
 - caseRedirect(url:String)
 - caseError(status:Int,message:String)
 - }
 - varrequest:WebRequest!
 - varresponse:WebResponse!
 - funchandleRequest(request:WebRequest,response:WebResponse){
 - self.request=request
 - self.response=response
 - defer{
 - response.requestCompletedCallback()
 - }
 - do{
 - switchtryAction(request.action){
 - caselet.View(templatePath,responseValues):
 - letvalues=responseValues
 - ifrequest.acceptJson{
 - tryresponse.outputJson(values)
 - }elseiflettemplatePath=templatePath{
 - tryresponse.renderHTML(templatePath,values:values)
 - }
 - caselet.Redirect(url):
 - response.redirectTo(url)
 - caselet.Error(status,message):
 - response.setStatus(status,message:message)
 - break;
 - }
 - }catchlete{
 - print(e)
 - }
 - }
 - funcAction(action:String)throws->IActionResult{
 - return.Error(status:500,message:"needimplement")
 - }
 - }
 
为何要这样做,这里有一个方法Action,根据URL Routing去找到对应的Action方法,之后通过handlerRequest处理返回对应的页面或者JSON数据,我们做一个HomeController.swift看看。
[plain]view plaincopy
print?
- importPerfectLib
 - classHomeController:Controller{
 - overridefuncAction(action:String)throws->IActionResult{
 - switchrequest.action{
 - case"about":
 - returntryAbout()
 - default:
 - returntryIndex()
 - }
 - }
 - funcIndex()throws->IActionResult{
 - varvalues=[String:Any]()
 - values["str"]="HelloSwiftMVCFramework"
 - return.View(templatePath:"Index.mustache",values:values)
 - }
 - funcAbout()throws->IActionResult{
 - varvalues=[String:Any]()
 - values["str"]="HelloSwiftMVCFramework"
 - return.View(templatePath:"About.mustache",values:values)
 - }
 - }
 
这里就是我们改造后的HomeController,而对应的URL Routing我参照.NET Core的方式放在Startup.swift上
[plain]view plaincopy
print?
- importPerfectLib
 - publicfuncPerfectServerModuleInit(){
 - Routing.Handler.registerGlobally();
 - Routing.Routes["GET",["/","/Home/{action}"]]={_inreturnHomeController()}
 - }
 
最后我们把页面加上 index.mustache
[html]view plaincopy
print?
- <!DOCTYPEhtml>
 - <htmllang="en">
 - <head>
 - <title>SwiftMVC</title>
 - </head>
 - <body>
 - <h1>{{str}}</h1>
 - </body>
 - </html>
 
about.mustache
[html]view plaincopy
print?
- <!DOCTYPEhtml>
 - <htmllang="en">
 - <head>
 - <title>About</title>
 - </head>
 - <body>
 - <h1>ThisisKinfeydesign</h1>
 - </body>
 - </html>
 
  基本上就可以完成我们的Rails改造了,看看在Xcode的结构,很Rails,很.NET Core吧
   看看运行的过程,如图
   我们运行下
   这里补充一点,如果你要把页面模版在Xcode中使用必须要对Build Phase进行设置,在copy Files中添加,需要设置Desination为Product Directory,如图
     今天说到这里,祝周末愉快!