Xitrum学习笔记12

时间:2022-12-26 21:45:00

一、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>

好像jade中,没有使用atJson好的方式,下列代码执行后,view无法获取user的login和name属性值

- var user = atJson("user")
h1 #{user}

#{user}会显示{&quot;login&quot;:&quot;admin&quot;,&quot;name&quot;:&quot;Admin&quot;}

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":"example@gmail.com","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也是如此。