Play 框架手册(8) – 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

下面的内容着重介绍一些非常重要的库。

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

 


前一篇:
后一篇:

发表评论