08.Play.libs库包
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
下面的内容着重介绍一些非常重要的库。
用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)) {
Stringplace = XPath.selectText("@city", place);
…
}
…
}
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();
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)
returnNone();
else
returnSome(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 Matchermatcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
if(matcher.matches()) {
returnSome(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)
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
StringaccessToken = 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。
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()) {
UserInfoverifiedUser = 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">
<labelfor="user">What’s your OpenID?</label>
<inputtype="text" name="user" id="user" />
<inputtype="submit" value="login…" />
</form>
</code>
路由定义为:
GET / Application.index
GET /login Application.login
* /authenticate Application.authenticate