play.libs 包包含了很多有用的库,以帮助实现通用的编程任务。
大多数据库都是简单的帮助类,而且非常易懂易用:
- Codec:数据编码和解码工具箱
- Crypto:密码图形工具(验证码?)
- Expression:动态评价表达式?
- F: java 实用编程工具
- Files:文件系统控制帮助类
- I18N:国际化帮助类
- IO:流控制帮助类
- Images:图片控制工具箱
- Mail: E-mail 功能
- MimeTypes: MIME 类型交换
- OAuth: OAuth 客户端协议(安全认证)
- OAuth2: OAuth2 客户端协议(安全认证)
- OpenID: OpenID 客户端协议(安全认证)
- Time: 时间和持续期间工具箱
- WS: 强大的 Web services 客户端
- XML: 加载 XML 结构
- XPath: 用 XPath 解析 XML
下面的内容着重介绍一些非常重要的库。
8.1. 用 XPath 解析 XML
XPath 或许是最容易解析 XML 文档的工具了,而且不需要使用代码生成工具。
play.libs.XPath 库为高效完成解析任务提供了所有需求。
XPath 可以操作所有的 org.w3.dom.Node 类型:
org.w3.dom.Document xmlDoc = ... // 找到 a Document somewhere
for(Node event: XPath.selectNodes("events//event", xmlDoc)) {
String name = XPath.selectText("name", event);
String data = XPath.selectText("@date", event);
for(Node place: XPath.selectNodes("//place", event)) {
String place = XPath.selectText("@city", place);
...
}
...
}
8.2. Web Service client
play.libs.WS 提供了一个强大的 http 客户端,Under the hood it uses Async HTTP client.
创建一个请求非常容易:
HttpResponse res = WS.url("http://www.google.com").get();
一旦获得 HttpResponse 对象后,就可以访问所有的 response 属性了:
int status = res.getStatus();
String type = res.getContentType();
也可获得不同类型的内容体:
String content = res.getString();
Document xml = res.getXml();
JsonElement json = res.getJson();
InputStream is = res.getStream();
也可以非阻塞方式使用 async API 来创建一个 http 请求。然后就可以得到一个 Promise<HttpResponse>, 一旦兑现成功, 就可以和平常一样使用 HttpResponse:
Promise<HttpResponse> futureResponse = WS.url(
"http://www.google.com"
).getAsync();
8.3. Functional programming with Java 功能扩展?
play.libs.F 库从功能编程(functional programming)带来了许多非常有用的 概念 constructs。这些概念用于处理复杂的抽象环境。这些概念之前也有涉及:
- Option<T> (T 值可设置也可不用设置)
- Either<A,B> (不是 A 就是 B)
- Tuple<A,B> (同是包含了 A 和 B)
Option<T>, Some<T> and None<T>
在某些情况下,函数可能不会返回结果(比如 find 方法),通常做法(非常糟 糕)是返回 null,这样做实际上非常危险,因为函数的返回类型不清晰。
Option<T>就是一个优雅的解决方案。如果函数成功执行,就返回明确的类型 Some<T>(封装了真实的结果),否则返回对象 None<T>(Option<T>的子类型)。
示例:
/* 安全除法(绝不抛出 ArithmeticException 运行时错误) */
public Option<Double> div(double a, double b) {
if (b == 0)
return None();
else
return Some(a / b);
}
用法:
Option<Double> q = div(42, 5);
if (q.isDefined()) {
Logger.info("q = %s", q.get()); // "q = 8.4"
}
这儿还有更便利的语法,这是因为 Option<T>实现了 Iterable<T>:
for (double q : div(42, 5)) {
Logger.info("q = %s", q); // "q = 8.4"
}
仅在 div 成功的情况下,for 循环才被执行一次。
Tuple<A, B>
Tuple<A, B>类包装了两种类型的对象 A 和 B。使用_1 和_2 域可以分别找回 A 和 B,如:
public Option<Tuple<String, String>> parseEmail(String email) {
final Matcher matcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
if (matcher.matches()) {
return Some(Tuple(matcher.group(1), matcher.group(2)));
}
return None();
}
然后:
for (Tuple<String, String> email : parseEmail("foo@bar.com")) {
Logger.info("name = %s", email._1); // "name = foo"
Logger.info("server = %s", email._2); // "server = bar.com"
}
T2<A, B>类是 Tuple<A, B>的别名。处理三个元素时使用 T3<A, B, C>类,一直 可到 T5<A, B, C, D, E>。
Pattern Matching 模式匹配
有些时候我们需要在 java 里进行模式匹配。 遗憾的是 java 没有内建的模式匹配 机制,java 也缺乏功能性概念,所以很难增加到库里。在这里,我们采取的方 案并不算太坏。
我们的主意是使用最后一次“for loop”语法来实现基本的模式匹配任务。模式 匹配必须检测对象是否匹配必须的条件,还要能提取感兴趣的值。play 使用的 模式匹配库位于 play.libs.F。
示例:
假设已经有一个对象类型的引用,现在想检测这个对象是否是 String 类 型,并且是以‘command:’字符串开始的。
标准方式为:
Object o = anything();
if(o instanceof String && ((String)o).startsWith("command:")) {
String s = (String)o;
System.out.println(s.toUpperCase());
}
使用 Play 模式匹配库,就可以这样写:
for(String s: String.and(StartsWith("command:")).match(o)) {
System.out.println(s.toUpperCase());
}
只有在条件符合的情况下, for 循环才会被执行一次, 并且会自动提取字符串值, 而不需要进行转换。 这是因为每个对象都是类型安全的, 并且由编译器进行检测, 不需要进行转换。
Promises
Promise 是 play 定制的“将来 Future”类型。事实上一个 Promise<T>类型也是 一个 Future<T>类型,因此,可以用作标准的 Future。但它拥有一个非常有趣的 属性:使用 onRedeem(„)方法能够获取注册返回的能力,该方法仅在允许的值 可用时才能被调用。
Promise 实例可以用在任何 Future 实例里(比如 Jobs, WS.async,等待)。
Promises 还可进行组合,比如:
Promise p = Promise.waitAll(p1, p2, p3)
Promise p = Promise.waitAny(p1, p2, p3)
Promise p = Promise.waitEither(p1, p2, p3)
8.4. OAuth
OAuth 是一个开源协议的安全 API 验证, 应用地桌面和 web 应用程序。
这里有两种不同的定义: OAuth 1.0 和 OAuth 2.0。Play 提供库来以消费者方式
连接至可能的服务。
标准步骤为:
- 跳转用户到提供者的验证页
- 在用户授权验证结束后,将直接返回到原来的服务器
- 你的服务器将会为当前用户交换修改已经验证的信息令牌, 这步操作是以 服务器至服务器的方式完成的。
play 会自动完成许多处理过程。
OAuth 1.0
OAuth 1.0 功能库是通过 play.libs.OAuth 类提供的,这个类基于 oauth-signpost。主要用于 Twitter 和 Google 的验证服务。
要想连接到一个服务,你需要使用下面的信息创建一个 OAuth.ServiceInfo 实 例,以获取 service provider:
- Request token URL
- Access token URL
- Authorize URL
- Consumer key
- Consumer secret
access token 可通过如下方式找到:
public static void authenticate() {
// TWITTER 是 OAuth.ServiceInfo 对象
// getUser() 用于返回当前用户
if (OAuth.isVerifierResponse()) {
// 得到 verifier 检验者
// 然后使用请求 tokens 得到 access tokens
OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken(getUser().token, getUser().secret);
// 存储它们并返回 index
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
index();
}
OAuth twitt = OAuth.service(TWITTER);
Response resp = twitt.retrieveRequestToken();
//得到未经授权的标志 tokens
//在继续之前先要保存
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
// 跳转用户到验证页
redirect(twitt.redirectUrl(resp.token));
}
现在可以使用 token 对通过分配的请求执行下面的调用:
mentions = WS.url(url).oauth(TWITTER, getUser().token,
getUser().secret).get().getString();
尽管这个事例没有进行错误检测,但在生产环境,还是应该进行检测。
OAuth.Response 对象拥有一个 error 域,错误发生的时候,其中含有错误的内 容。很多情况下不能给用户授权的原因是提供者已经下线或错误太多。
完整的示例见 samples-and-tests/twitter-oauth。
OAuth 2.0
OAuth 2.0 比 OAuth 1.0 更简单,这是因为它不需要调用 signing 请求。主要用 于 Facebook 和 37signals。
play.libs.OAuth2 类提供了该支持。
为了连接到服务,你需要使用下面的信息创建一个 OAuth2 实例,从服务提供者 获取:
- Access token URL
- Authorize URL
- Client ID
- Secret
public static void auth() {
// FACEBOOK 是一个 OAuth2 对象
if (OAuth2.isCodeResponse()) {
// authUrl 必须和 retrieveVerificationCode 调用一致
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
//如果有错误发生则为 null
String accessToken = response.accessToken;
//调用成功则为 null
OAuth2.Error = response.error;
//存储 accessToken,在请求服务时要用到
index();
}
// authUrl 是一个包含了服务绝对 URL 的字符串
// 应该跳转回来
// 下面将触发一个跳转
FACEBOOK.requestVerificationCode(authUrl);
}
一旦获取当前用户的 access token,就可以用它来查询服务:
WS.url(
"https://graph.facebook.com/me?access_token=%s", access_token
).get().getJson();
完整示例见 samples-and-tests/facebook-oauth2。
8.5. OpenID
OpenID 是一个开源的分散式身份认证系统。在你的系统里不需要保存特定用户 的信息就可以很容易接受新的用户。 只需通过它们的 OpenID 跟踪验证用户即可。
本示例提供一个轻量级的演示,用于演示如果使用 OpenID 验证用户:
- 对每个请求,检测用户是否已经连接
- 如果没有,就显示用户可以提交 OpenID 的页面
- 跳转用户到 OpenID 提供者
- 当用户回来的时候,获取验证的 OpenID 并存储到 HTTP session 里
play.libs.OpenID 类提供了 OpenID 功能:
@Before(unless={"login", "authenticate"})
static void checkAuthenticated() {
if(!session.contains("user")) {
login();
}
}
public static void index() {
render("Hello %s!", session.get("user"));
}
public static void login() {
render();
}
public static void authenticate(String user) {
if(OpenID.isAuthenticationResponse()) {
UserInfo verifiedUser = OpenID.getVerifiedID();
if(verifiedUser == null) {
flash.error("Oops. Authentication has failed");
login();
}
session.put("user", verifiedUser.id);
index();
} else {
if(!OpenID.id(user).verify()) { // will redirect the user
flash.error("Cannot verify your OpenID");
login();
}
}
}
login.html 模板代码为:
#{if flash.error}
<h1>${flash.error}</h1>
#{/if}
<form action="@{Application.authenticate()}" method="POST">
<label for="user">What’s your OpenID?</label>
<input type="text" name="user" id="user" />
<input type="submit" value="login„" />
</form>
</code>
路由定义为:
GET / Application.index
GET /login Application.login
* /authenticate Application.authenticate