一、Request
参数种类
1. 文本参数:名为textParams,类型为 scala.collection.mutable.Map[Sting, Seq[String]]
1)queryParams:URL中 ? 后面的参数,例:http://example.com/blah?x=1&y=2
2)bodyTextParams:在POST请求体里的参数
3)pathParams:嵌入到URL的参数,例:GET("articles/:id/:title")
从1)到3),同名参数会被后面的覆盖
2. 上传文件参数(二进制):名为bodyFileParams,类型为scala.collection.mutable.Map[String, Seq[FileUpload]]
访问参数
在Action中可以直接访问以上参数,或使用访问方法
1. 访问 textParams:
? param("x"): 返回 String, 如果x不存在则抛出异常
? paramo("x"): 返回 Option[String]
? params("x"): 返回 Seq[String], 如果x不存在返回Seq.empty
可以使用param[Int]("x"),params[Int]("x")等等方法把文本参数转化为Int, Long, Fload, Double等其他类型。
要把文本参数转化为更多的类型,要重写convertTextParam方法
2. 对于upload file,param[FileUpload]("x"), params[FileUpload]("x") etc.
"at"
在处理请求的过程中可以使用 at 传递数据。at 的类型是 scala.collection.mutable.HashMap[String, Any]。这里的 at 是Rails的@的克隆。
示例代码:
Articles.scala
@GET("articles/:id") class ArticlesShow extends AppAction { def execute() { val (title, body) = ... // Get from DB at("title") = title respondInlineView(body) } }
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>{if (at.isDefinedAt("title")) "My Site - " + at("title") else "My Site"}</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
atJson
atJson方法可以自动把 at("key")的值转化成JSON,它在从Scala传递model到JavaScript时很有用
atJson("key") 和 xitrum.util.SeriDeseri.toJson(at("key")) 是等价的
Action.scala
case class User(login: String, name: String) ... def execute() { at("user") = User("admin", "Admin") respondView() } ...
Action.ssp
<script type="text/javascript"> var user = ${atJson("user")}; alert(user.login); alert(user.name); </script>
RequestVar
以上at的用法不是类型安全的,因为你可以把任意类型的值放到HashMap中。
为了类型安全,应该使用RequestVar包装at
RVar.scala
import xitrum.RequestVar object RVar { object title extends RequestVar[String] }
Articles.scala
@GET("articles/:id") class ArticlesShow extends AppAction { def execute() { val (title, body) = ... // Get from DB RVar.title.set(title) respondInlineView(body) } }
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>{if (RVar.title.isDefined) "My Site - " + RVar.title.get else "My Site"}</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
二、Cookie
在Action中,使用requestCookies(Map[String, String]类型)获取从浏览器发出的cookie
requestCookies.get("myCookie") match { case None => ... case Some(string) => ... }
向浏览器发送cookie,要创建一个DefaultCookie实例并把它添加到 responseCookies(一个包含Cookie的ArrayBuffer)
val cookie = new DefaultCookie("name", "value") cookie.setHttpOnly(true) // true: JavaScript cannot access this cookie responseCookies.append(cookie)
如果没有调用cookie.setPath(cookiePath)设置cookie的路径,它的路径会被设置成站点的根路径(xitrum.Config.withBaseUrl("/"))。
这样可以避免cookie重复(为什么?)
要删除一个浏览器发送的cookie, 发送一个同名cookie并把它的过期时间(max age)设置成0,浏览器会立刻让这个cookie过期。
要浏览器在关闭时删除cookie,把cookie的过期时间设置成 Long.MinValue
cookie.setMaxAge(Long.MinValue)
IE不支持"max-age",但是Netty会对此进行检测,对浏览器适当地输出"max-age"或"expires"。
浏览器不会把cookie属性发回给服务器,它只发送cookie的 名-值 对。
如果想通过给cookie值签名的方式以防止被篡改,使用
xitrum.util.SeriDeseri.toSecureUrlSafeBase64 和 xitrum.util.SeriDeseri.fromSecureUrlSafeBase
具体内容参照 Xitrum学习笔记23。
Cookie中被允许的字符
不能再cookie中使用任意的字符。例如,如果要使用UTF-8字符,需要对他们进行编码。可以使用xitrum.utill.UrlSafeBase64或xitrum.util.SeriDeseri。
写cookie的例子:
import io.netty.util.CharsetUtil import xitrum.util.UrlSafeBase64 val value = """{"identity":"[email protected]","first_name":"Alexander"}""" val encoded = UrlSafeBase64.noPaddingEncode(value.getBytes(CharsetUtil.UTF_8)) val cookie = new DefaultCookie("profile", encoded) responseCookies.append(cookie)
读cookie的例子:
requestCookies.get("profile").foreach { encoded => UrlSafeBase64.autoPaddingDecode(encoded).foreach { bytes => val value = new String(bytes, CharsetUtil.UTF_8) println("profile: " + value) } }
三、Session
Session的存储、恢复、加密由Xitrum自动完成,无需我们操心。
在Action中,可以使用session,它是scala.collection.mutable.Map[String,Any]的一个实例。在session中的东西必须是可序列化的。
例如,要标记用户已登录,可以把他的用户名放入session
session("userId") = userId
要检查用户是否还处于登录状态中,只要检查他的session中是否还有用户名
if (session.isDefinedAt("userId")) println("This user has logged in")
在每次访问时,存储用户ID并且从数据库中获取用户信息是一个好的做法。
That way changes to the
user are updated on each access (including changes to user roles/authorizations). ???
session.clear()
为了防止session固定攻击,在登录Action和登出Action都调用session.clear()
@GET("login") class LoginAction extends Action { def execute() { ... session.clear() // Reset first before doing anything else with the session session("userId") = userId } }
SessionVar
SessionVar, 和RequestVar一样, 是一种使session更加类型安全的一种方式。
例如,要在用户登录后保存username到session中
声明session变量
import xitrum.SessionVar object SVar { object username extends SessionVar[String] }
登录成功后
SVar.username.set(username)
显示用户名
if (SVar.username.isDefined) <em>{SVar.username.get}</em> else <a href={url[LoginAction]}>Login</a>
删除session变量:SVar.username.remove()
重置整个session:session.clear()
保存session
Xitrum提供3中session存储方式。在 config/xitrum.conf中,可以对session存储方式进行配置:
CookieSessionStore:在客户端保存session
# Store sessions on client side store = xitrum.scope.session.CookieSessionStore
LruSessionStore:在服务端内存保存session
# Simple in-memory server side session store store { "xitrum.local.LruSessionStore" { maxElems = 10000 } }
如果在一个集群中运行多个服务器,可以使用Hazelcast保存集群可感知的session。
参照 https://github.com/xitrum-framework/xitrum-hazelcast
当使用CookieSessionStore或Hazelcast时,session数据必须是可序列化的。如果必须存储非序列化的内容,要使用LruSessionStore。
如果使用了LruSessionStore,还想运行有多个服务器的集群时,必须使用支持粘性会话的负载均衡器(load balancer that supports sticky sessions这是什么东东??)
正常情况下,这三种默认的会话存储方式足够了。如果要实现自定义的session存储,需要继承SessionStore或ServerSessionStore并实现他们的抽象方法。
配置Session存储方式有两种形式:
store = my.session.StoreClassName ##或者 store { "my.session.StoreClassName" { option1 = value1 option2 = value2 } }
能把session存在客户端cookie的就尽量存在客户端(内容必须可序列化且小于4KB),因为这样更可扩展。
只有必须把session存在server端(内存或DB)时才这样存储。
客户端存储Session和服务器端存储session的对比
有2种存储session的方式:
1. 只在客户端存储
- Session数据被存储在客户端中URL编码的cookie里
- 服务器端不需要存储
- 当请求到来时,服务器端从cookie里解码出session数据
2. 客户端和服务器端都存储
- 一个Session有两个部分:session ID 和 session数据
- 服务器保持session存储,类似于包含 ID->数据 的检索表
- SessionID也被存储在客户端中URL编码的cookie里
- 当请求到来时,服务器端从cookie中解码出ID,再用ID去查看对应的数据
- 类似于信用卡,信用卡里的不存有钱,只存有一个ID
这两种存储session的方式中,客户端都要存储session的一些内容到cookie中(ID、数据)。
object和val
关于请求、Session、Cookie的操作,要使用object代替val
不要这样写代码
object RVar { val title = new RequestVar[String] val category = new RequestVar[String] } object SVar { val username = new SessionVar[String] val isAdmin = new SessionVar[Boolean] }
以上代码可以通过编译,但是不能正确工作,因为变量内部使用类名去查找。使用val时,title和category就有了相同的类名“xitrum.RequestVar”。
username和isAdmin也是如此。