Xitrum学习笔记03 - Action和View
Action:
Xitrum 提供了3种Action:普通Action, FutureAction 和 ActorAction
它们都是Trait,而不是 Class
1. 普通Action:
当请求到来时,Action实现类直接在Netty的 IO线程上运行,不能用普通Action来执行 消耗很长时间的处理,否则Netty就不能接收新的连接或发出响应到客户端
import xitrum.Action import xitrum.annotation.GET @GET("hello") class HelloAction extends Action { def execute() { respondText("Hello") } }
2. FutureAction:
扩展了 Action,FutureAction实现类异步运行在和ActorAction相同的线程池上,这个线程池和Netty线程池是分离的
FutureAction实现类的执行context是 xitrum.Config.actorSystem.dispatcher.
trait FutureAction extends Action
import xitrum.FutureAction import xitrum.annotation.GET @GET("hello") class HelloAction extends FutureAction { def execute() { respondText("hi") } }
3. ActorAction:
trait ActorAction extends Actor with Action
实现类是一个 Akka actor。
当有需求时,一个actor实例被创建。当连接关闭时,或者响应通过 respondText/respondView 等方法被发出时,actor会被停止。
对于分块的响应,actor会在最后一个块儿的响应发出后才被停止。
actor运行在命名为"xiturm"的Akka actor系统的线程池中。
1 import scala.concurrent.duration._ 2 import xitrum.ActorAction 3 import xitrum.annotation.GET 4 @GET("actor") 5 class HelloAction extends ActorAction { 6 def execute() { 7 // See Akka doc about scheduler 9 context.system.scheduler.scheduleOnce(3.seconds, self, System.currentTimeMillis()) 10 // See Akka doc about "become" 11 context.become { 12 case pastTime => 13 respondInlineView(s"It's $pastTime Unix ms 3s ago.") 14 } 15 } 16 }
响应(Response)方式:
1. 对于action来说,要向客户端发出响应,可以调用如下方法
• respondView: responds view template file, with or without layout
• respondInlineView: responds embedded template (not separate template file), with or without layout
• respondText("hello"): responds a string without layout
• respondHtml("<html>...</html>"): same as above, with content type set to “text/html”
• respondJson(List(1, 2, 3)): converts Scala object to JSON object then responds
• respondJs("myFunction([1, 2, 3])")
• respondJsonP(List(1, 2, 3), "myFunction"): combination of the above two
• respondJsonText("[1, 2, 3]")
• respondJsonPText("[1, 2, 3]", "myFunction")
• respondBinary: responds an array of bytes
• respondFile: sends a file directly from disk, very fast because zero-copy (aka send-file) is used
• respondEventSource("data", "event")
2. 响应返回事先创建好的View模板文件内容
要运用这种方式,每个Action scala文件都要对应一个View jade文件。代码示例如下:
scr/main/scala/mypackage/MyAction.scala:
package mypackage import xitrum.Action import xitrum.annotation.GET @GET("myAction") class MyAction extends Action { def execute() { respondView() } def hello(what: String) = "Hello %s".format(what) }
scr/main/scalate/mypackage/MyAction.jade:
- import mypackage.MyAction !!! 5 html head != antiCsrfMeta != xitrumCss //包含了Xitrum默认的CSS。这个CSS是可以移除的 != jsDefaults //包含了 jQuery、jQuery验证插件等等内容,要放在<head>标签中 title Welcome to Xitrum body a(href={url}) Path to the current action p= currentAction.asInstanceOf[MyAction].hello("World") //将currentAction转换为MyAction,并调用其hello方法 != jsForView //包含了由jsAddToView标签添加的JavaScript代码,jsForView标签应该放在整个layout的底部
在View模板中,可以使用 xitrum.Action中定义的所有方法,而且也可以使用由Scalate提供的一些utility方法(参考 Scalate doc)
默认的 Scalate 模板类型是 Jade,其他的有 Mustache, Scaml 和 Ssp。在config/xitrum.conf文件中配置默认模板类型.
# Comment out if you don't use template engine. template { "xitrum.view.Scalate" { defaultType = jade # jade, mustache, scaml, or ssp } }
也可以在Action实现类调用 respondView时指定模板类型
val options = Map("type" ->"mustache")
respondView(options)
如果需要在View中多次调用Action实现类的的方法的话,只需要将currentAction做一次转换,如:
- val myAction = currentAction.asInstanceOf[MyAction]; import myAction._ p= hello("World") p= hello("Scala") p= hello("Xitrum")
Layout
利用respondView或respondInlineView提交一个View时,Xitrum会把View内容表示成一个String,然后把这个String设置给renderedView变量。
Xitrum调用当前Action的layout方法,把方法的结果返回给浏览器。如果当前Action的layout方法没被调用,则没有结果返回给浏览器。
默认的layout方法只返回 renderedView 本身,可以通过重写layout方法以添加关于view的其他内容。如果重写的layout方法中包含renderedView,它只是作为layout的一部分内容。
layout方法在action的view生成之后才被调用,layout方法返回的内容就是要响应到浏览器中的内容。
可以通过创建一个继承自Action的Trait,并将其相应的View模板文件定义为通用layout。再让其他Action scala实现这个Trait,这样其他Action的页面就会包含这个通用的layout。
示例:
src/main/scala/mypackage/AppAction.scala
package mypackage import xitrum.Action trait AppAction extends Action { override def layout = renderViewNoLayout[AppAction]() //将AppAction.jade中的内容作为页面内容的模板 }
src/main/scalate/mypackage/AppAction.jade
!!! 5
html
head
!= antiCsrfMeta
!= xitrumCss
!= jsDefaults
title Welcome to Xitrum
body
!= renderedView -#如果没有这一句,使用这个模板的其他View的内容不会呈现
!= jsForView
src/main/scala/mypackage/MyAction.scala
package mypackage import xitrum.annotation.GET @GET("myAction") class MyAction extends AppAction { def execute() { respondView() } def hello(what: String) = "Hello %s".format(what) }
scr/main/scalate/mypackage/MyAction.jade:
- import mypackage.MyAction a(href={url}) Path to the current action p= currentAction.asInstanceOf[MyAction].hello("World")
此示例验证成功,AppAction.jade中的body需要含有!=renderedView
layout不在View模板里的示例(直接写到 Action Scala文件里)
示例1:
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
示例2:
val specialLayout = () => DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) respondView(specialLayout _)
Inline view
import xitrum.Action import xitrum.annotation.GET
@GET("myAction")
class MyAction extends Action {
def execute() {
val s = "World" // Will be automatically HTML-escaped
respondInlineView(
<p>Hello <em>{s}</em>!</p>
)
}
}
Render fragment
有两个Jade View文件:
scr/main/scalate/mypackage/MyAction.jade,scr/main/scalate/mypackage/_MyFragment.jade
如果想提交fragment文件到相同路径下的其他Jade文件里,可以使用
renderFragment[MyAction]("MyFragment")
如果在这种情况下,MyAction是当前action,则上面的代码可以写成 renderFragment("MyFragment")
返回其他action的View
使用respondView[ClassName]()和redirectTo[ClassName]()
示例:
package mypackage import xitrum.Action import xitrum.annotation.{GET, POST} @GET("login") class LoginFormAction extends Action { def execute() { // Respond scr/main/scalate/mypackage/LoginFormAction.jade respondView() } } @POST("login") class DoLoginAction extends Action { def execute() { val authenticated = ... if (authenticated) redirectTo[HomeAction]() // 定向到HomeAction的View else // Reuse the view of LoginFormAction respondView[LoginFormAction]() //定向到LoginForAction的View } }
redirectTo和respondView的区别:
redirectTo:不需要定向到的Action提供相应的模板;默认HttpResponseStatus是302
respondView:需要定向到的Action提供相应的模板;HttpResponseStatus是200
也可以从一个Action,定向到多个View
package mypackage import xitrum.Action import xitrum.annotation.GET // These are non-routed actions, for mapping to view template files: // scr/main/scalate/mypackage/HomeAction_NormalUser.jade // scr/main/scalate/mypackage/HomeAction_Moderator.jade // scr/main/scalate/mypackage/HomeAction_Admin.jade trait HomeAction_NormalUser extends Action trait HomeAction_Moderator extends Action trait HomeAction_Admin extends Action @GET("") class HomeAction extends Action { def execute() { val userType = ... userType match { case NormalUser => respondView[HomeAction_NormalUser]() case Moderator => respondView[HomeAction_Moderator]() case Admin => respondView[HomeAction_Admin]() } } }
使用respondView[HomeAction_NormalUser]()这样的写法是 类型安全的 写法,也可以使用String参数把scalate的View的位置传给respondView方法
respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin")
Component
Component是在各个View中复用的组件,和Action有些类似
- Component没有routes(@GET("")括号中的内容就是route),因此不需要使用execute方法
- 因为Component只是提交一个View的片段(fragment),而不是发出整个的响应内容,所以在Component要调用renderXXX来代替respondXXX
- 一个Component可以没有、有一个 或 有多个关联的View模板
示例:
package mypackage import xitrum.{FutureAction, Component} import xitrum.annotation.GET class CompoWithView extends Component { def render() = { // Render associated view template, e.g. CompoWithView.jade // Note that this is renderView, not respondView! renderView() } } class CompoWithoutView extends Component { def render() = { "Hello World" } } @GET("foo/bar") class MyAction extends FutureAction { def execute() { respondView() } }
MyAction.jade:
- import mypackage._ != newComponent[CompoWithView]().render() != newComponent[CompoWithoutView]().render()