举个小例子,先熟悉一下:
import javax.servlet.http.{HttpServlet, HttpServletRequest => HSReq, HttpServletResponse => HSResp} class HelloScalaServlet extends HttpServlet { override def doGet(req : HSReq, resp : HSResp) = resp.getWriter().print("<HTML>" + "<HEAD><TITLE>Hello, Scala!</TITLE></HEAD>" + "<BODY>Hello, Scala! This is a servlet.</BODY>" + "</HTML>") }
这个与Java的版本很像,是不是很熟悉
注意,我使用了一些适当的导入别名来缩短请求的类型名称和相应类型;除此之外,这个 servlet 几乎与其 Java servlet 形式一样。编译时请记得在 servlet-api.jar(通常随 servlet 容器一起发布;在 Tomcat 6.0 发行版中,它隐藏在 lib 子目录中)中包含一个引用,否则将找不到 servlet API 类型。
这还准备得不够充分;根据 servlet 规范,它必须使用一个 web.xml 部署描述符部署到 Web 应用程序目录中(或一个 .war 文件中),该描述符描述 servlet 应该与哪个 URL 结合。对于这样一个简单的例子,使用一个相当简单的 URL 来配合它最容易:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>helloWorld</servlet-name> <servlet-class>HelloScalaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>helloWorld</servlet-name> <url-pattern>/sayHello</url-pattern> </servlet-mapping> </web-app>
当然,格式良好的 HTML 与格式良好的 XML 非常相似;鉴于这一点,Scala 对 XML 字面值的支持使编写这个 servlet 简单得多(参阅 参考资料中的
“Scala 和 XML” 一文)。Scala 不是在传递给 HttpServletResponse
的
String 中直接嵌入消息,它可以分离逻辑和表示形式(非常简单),方法是利用此支持将消息放在 XML 实例中,然后再传递回去:
import javax.servlet.http.{HttpServlet, HttpServletRequest => HSReq, HttpServletResponse => HSResp} class HelloScalaServlet extends HttpServlet { def message = <HTML> <HEAD><TITLE>Hello, Scala!</TITLE></HEAD> <BODY>Hello, Scala! It's now { currentDate }</BODY> </HTML> def currentDate = java.util.Calendar.getInstance().getTime() override def doGet(req : HSReq, resp : HSResp) = resp.getWriter().print(message) }
实际上,Scala 编译器与 XML 对象消息一起整合到一个 scala.xml.Node
中,然后在将它传递给响应的 Writer
的 print
方法时将其转换为一个
String。
不要小看这一点 — 表达形式从逻辑中分离出来完全在一个类内部进行。这条 XML 消息将进行编译时检查,以确保语法正确和格式良好,并获得一些标准 servlet(或 JSP)不具备的好处。由于 Scala 可以进行类型推断,因此可以省略有关 message
和 currentDate
的实际类型消息,使得这就像动态语言
Groovy/Grails 一样。初次使用效果不错。
在servlet中获得客户端发来的参数
这个方法不会在任何用户界面设计大赛中夺冠,但是它达到了目的:这是一个 HTML 表单,它会将数据发送给一个新的 Scala servlet(绑定到 sayMyName 的相对 URL)。这些数据将根据 servlet 规范存储在一个名称-值对集合中,可通过 HttpServletRequest.getParameter()
API
调用获得。在此调用中,我们将 FORM
元素的名称作为一个参数传递给
API 调用。
class NamedHelloWorldServlet1 extends HttpServlet { def message(firstName : String, lastName : String) = <HTML> <HEAD><TITLE>Hello, {firstName} {lastName}!</TITLE></HEAD> <BODY>Hello, {firstName} {lastName}! It is now {currentTime}.</BODY> </HTML> def currentTime = java.util.Calendar.getInstance().getTime() override def doPost(req : HSReq, resp : HSResp) = { val firstName = req.getParameter("firstName") val lastName = req.getParameter("lastName") resp.getWriter().print(message(firstName, lastName)) } }
但这缺少了我之前提到的消息分离的一些好处,因为现在消息定义必须显式使用参数 firstName
和 lastName
;如果响应
get 中使用的元素个数超过 3 个或 4 个,情况就会变得比较复杂。此外,doPost
方法在将参数传递给消息进行显示之前,必须自行提取每一个参数
— 这样的编码很繁琐,并且容易出错。
abstract class BaseServlet extends HttpServlet { import scala.collection.mutable.{Map => MMap} def message : scala.xml.Node; protected var param : Map[String, String] = Map.empty protected var header : Map[String, String] = Map.empty override def doPost(req : HSReq, resp : HSResp) = { // Extract parameters // val m = MMap[String, String]() val e = req.getParameterNames() while (e.hasMoreElements()) { val name = e.nextElement().asInstanceOf[String] m += (name -> req.getParameter(name)) } param = Map.empty ++ m // Repeat for headers (not shown) // resp.getWriter().print(message) } } class NamedHelloWorldServlet extends BaseServlet { override def message = <HTML> <HEAD><TITLE>Hello, {param("firstName")} {param("lastName")}!</TITLE></HEAD> <BODY>Hello, {param("firstName")} {param("lastName")}! It is now {currentTime}. </BODY> </HTML> def currentTime = java.util.Calendar.getInstance().getTime() }
当然,错误处理是处理 Web 应用程序 FORM
的重要部分,而
Scala 作为一种函数性语言,保存的内容都是表达式,这意味着我们可以将消息编写为结果页面(假设我们喜欢这个输入),或编写为错误页面(如果我们不喜欢这个输入)。因此,检查 firstName
和 lastName
的非空状态的验证函数可能
class NamedHelloWorldServlet extends BaseServlet { override def message = if (validate(param)) <HTML> <HEAD><TITLE>Hello, {param("firstName")} {param("lastName")}! </TITLE></HEAD> <BODY>Hello, {param("firstName")} {param("lastName")}! It is now {currentTime}.</BODY> </HTML> else <HTML> <HEAD><TITLE>Error!</TITLE></HEAD> <BODY>How can we be friends if you don't tell me your name?!?</BODY> </HTML> def validate(p : Map[String, String]) : Boolean = { p foreach { case ("firstName", "") => return false case ("lastName", "") => return false //case ("lastName", v) => if (v.contains("e")) return false case (_, _) => () } true } def currentTime = java.util.Calendar.getInstance().getTime() }
注意,模式匹配可以使编写比较简单的验证规则变得很容易。利用模式匹配绑定到原始值(比如上一个例子),或者绑定到一个本地变量(比如我们要排除任何姓名中有 “e” 的人,比如上一个注释)。
显然,还有事情需要做!困扰 Web 应用程序的典型问题之一是 SQL 注入攻击,它由通过 FORM
传入的未转义
SQL 命令字符引入,并且在数据库中执行之前连接到包含 SQL 结构的原始字符串。使用 scala.regex 包中的正则表达式支持可以确认 FORM
验证是否正确。事实上,整个验证过程会提交给使用默认验证实现的基类,该验证实现默认情况下只返回
true(因为 Scala 是函数性语言,所以不要忽略好的对象设计方法)。