1 of 31

Play2 WS 再入門

2014 / 5 / 24 (Sat)

注意:LT(ライトニングトーク)のため、

光速談話ご容赦ください

2 of 31

アジェンダ

  • 自己紹介
  • 今日の目標結果
  • Play2.2-WS 再入門(本題)
  • まとめ

3 of 31

自己紹介

  • 西野 伸作
  • @magnet88jp�
  • 趣味
    • ニコ動
      • 将棋
      • 麻雀
      • 政治

4 of 31

経歴

  • 愛知県出身
  • 静岡大学 物質工学先攻
  • 上京後、日立システム、シャノンに勤務
  • 現在は、ネットイヤーグループに在籍

 普段は、Salesforceの導入をやっています。

 (SalesforceといえばHeroku、HerokuといえばPlayframework)

5 of 31

タイトルのWSについて

  • WSは、Web Serviceアクセス用ライブラリ
  • パッケージ名:play.api.libs.ws

  • 外部のWeb Serviceにアクセス�するためのライブラリ群です。

  • 再入門というのは、�Play1-WSの説明ブログ以来

6 of 31

今日の目標と結果

  • 目標
    • Play2のWSを使って他のWebサービスにつなぐこと�
    • WSを使用するための知識をつけること

7 of 31

今日の目標と結果

  • 結果
    • WSライブラリを使って、OAuth2経由でSalesforceに接続、Caseデータを取得�(個人的にはできた)�
    • あとは、WSを使用するための�知識を、、

8 of 31

Play2.2-WS 再入門

  • 教科書
    • Play 2.2 WS(公式)
    • (情報求む!!)

9 of 31

WS 再入門に必要な基礎知識

今回は5つ

1. Action

    • コントローラのアクションとリダイレクト

2. scala.concurrent.Future

    • 非同期処理(Play2.0でいうPromise)

3. play.api.libs.json.Reads

    • jsonパーサ

(つづく)

10 of 31

WS 再入門に必要な基礎知識(つづき)

4. Implicit Parameter

    • 暗黙のパラメータ

5. Option

    • トラブルシュート(jsonの値にnullを含む場合)

(外部サービスのAPIキーの取得方法は省略します)

11 of 31

1. Action

  • Webサービスを利用する際のControllerの操作

1. 認証画面へのリダイレクト

2. 受け取ったレスポンスから再リクエスト

conf/routes

GET /login controllers.Application.login

GET /auth/salesforce/callback controllers.Application.apiCall(code :String)

12 of 31

1. Action(認証画面へリダイレクト)

val loginUrl = "https://login.salesforce.com/services/oauth2/authorize"

val redirectUrl = "http://localhost:9000/auth/salesforce/callback"

val clientId = "REPLACE_TO_YOUR_CLIENT_ID"

def login = Action {

val url = loginUrl + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirectUrl

Redirect(url)

}

13 of 31

1. Action(レスポンスから再リクエスト)

val tokenUrl = "https://login.salesforce.com/services/oauth2/token"

val clientSecret = "REPLACE_TO_YOUR_CLIENT_SECRET"

def apiCall(code :String) = Action.async {

val url = tokenUrl + "?code=" + code + "&grant_type=authorization_code&client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=" + redirectUrl

WS.url(url).get().flatMap { response =>

val sfdc :SFDCToken = (response.json).as[SFDCToken]

val reqUrl = sfdc.instance_url + "/services/data/v29.0/query/"

val atoken = "OAuth " + sfdc.access_token

val query = "SELECT Id, Subject, Description FROM Case"

WS.url(reqUrl).withHeaders("Authorization" -> atoken).withQueryString("q" -> query).get().map {

response2 => Ok(response2.body)

}}}

14 of 31

1. Action(ポイント)

  • WSを使ってリクエストする場合は、非同期処理(外部Webサービスのレスポンスを待つ)になるので Action.asyncを使う
  • WS.url(url).get() の戻り値は Future[Response]

Future[Response]型で受け取る場合

val futureResponse :Future[Response] = WS.url(url).get()

15 of 31

2. scala.concurrent.Future

  • WS APIは非同期でHTTP通信するため、とりあえずFutureオブジェクトを返す
  • Futureオブジェクトは非同期の通信が終わった後にResponseを返す
  • Responseに対して何か処理を行いたい時は、Futureオブジェクトのmapメソッドにより、関数(いわゆるコールバック関数)を渡すことで処理ができる
    • Scalaの関数は { IN => OUT }で表現

16 of 31

2. scala.concurrent.Future

WS.url(url).get().flatMap { response =>

val sfdc :SFDCToken = (response.json).as[SFDCToken]

val reqUrl = sfdc.instance_url + "/services/data/v29.0/query/"

val atoken = "OAuth " + sfdc.access_token

val query = "SELECT Id, Subject, Description FROM Case"

WS.url(reqUrl).withHeaders("Authorization" -> atoken)

.withQueryString("q" -> query).get().map {

response2 => Ok(response2.body)

}

}

IN

OUT

OUT

IN

17 of 31

2. scala.concurrent.Future(注意点)

  • mapを入れ子で使用する場合は、flatMapを使用する
    • mapのままだと、内側の戻り値がFuture[Future[Result]]になるため

Listの例

scala> List(1,2,3) map { x => List(1,2) map( x * _) }

res1: List[List[Int]] = List(List(1, 2), List(2, 4), List(3, 6))

scala> List(1,2,3) flatMap { x => List(1,2) map( x * _) }

res3: List[Int] = List(1, 2, 2, 4, 3, 6)

18 of 31

3. play.api.libs.json.Reads

  • 受け取ったレスポンス(JSON)を処理する�
  • ReadsはJsonのマーシャルをやってくれる
    • マーシャルは、Jsonからcaseオブジェクトへの変換作業
  • (ちなみにWrites(アンマーシャル)もある)

  • Readsは、Inplicit Parameterで定義

19 of 31

3. play.api.libs.json.Reads

case class SFDCToken(id: String, issued_at: String, refresh_token: Option[String], instance_url: String, signature: String, access_token: String)

implicit val sfdcTokenReads: Reads[SFDCToken] = Json.reads[SFDCToken]

WS.url(url).get().flatMap { response =>

val sfdc :SFDCToken = (response.json).as[SFDCToken]

val reqUrl = sfdc.instance_url + "/services/data/v29.0/query/"

val atoken = "OAuth " + sfdc.access_token

val query = "SELECT Id, Subject, Description FROM Case"

(中略)

}}

JSON

case class

Reads

20 of 31

3. play.api.libs.json.Reads(ポイント)

case class SFDCToken(id: String, issued_at: String, refresh_token: Option[String],

instance_url: String, signature: String, access_token: String)

implicit val sfdcTokenReads: Reads[SFDCToken] = Json.reads[SFDCToken]

  • Json.readsメソッドは内部的に以下と同じ処理を行う

implicit val sfdcTokenReads: Reads[SFDCToken] = (

(__ \ "id").read[String]

and (__ \ "issued_at").read[String]

and (__ \ "refresh_token").read[String]

and (__ \ "instance_url").read[String]

and (__ \ "signature").read[String]

and (__ \ "access_token").read[String]

)(SFDCToken)

JSONの構造をマップ

21 of 31

4. Implicit Parameter

  • 暗黙のパラメータは、Scalaの強力な推論エンジンにより、スコープ内にそれっぽい型が定義されていれば、パラメータとして適用される機能です。

WS.url(url).get().flatMap { response =>

val sfdc :SFDCToken = (response.json).as[SFDCToken]

(中略)

}

注目

JSON

case class

22 of 31

4. Implicit Parameter

!?

23 of 31

4. Implicit Parameter

「as」 だけで

マーシャリング!?

24 of 31

4. Implicit Parameter

いいえ、「暗黙パラメータ」です。

implicit val sfdcTokenReads: Reads[SFDCToken] = Json.reads[SFDCToken]

WS.url(url).get().flatMap { response =>

val sfdc :SFDCToken = (response.json).as[SFDCToken] (sfdcTokenReads)

(中略)

}

25 of 31

5. Option

(トラブルシューティング)

レスポンスのJsonに項目が含まれないときがある場合

定義:6つ

case class SFDCToken(id: String, issued_at: String, refresh_token: String,

instance_url: String, signature: String, access_token: String)

実際のJSON:5つ

{ “id”:”https://login.salesforce.com/id/00D10000000ZlJIEA0/005100000029TlhAAE”, “issued_at”:”1400685904843”, “instance_url”:”https://ap.salesforce.com”, “signature":"eIh1n+JjbFIMfEZQoE6hgW2lwuEWmn7iklS1Ue7FitE=",

"access_token":"00D10000000ZlJI!AR4AQLuB8z3DBjRCq25YyOObiPAE"}

26 of 31

5. Option

27 of 31

5. Option

その場合は、Optionをつけてあげましょう

case class SFDCToken(id: String, issued_at: String, refresh_token: Option[String],

instance_url: String, signature: String, access_token: String)

  • Optionは、存在したらSome(値)を返し、存在しなければNoneを返すラッパークラス
  • 値を取得する場合は、getOrElseメソッドで値がない場合を考慮して値を取得できます

val rtoken :String = sfdc.refresh_token.getOrElse(“”)

28 of 31

まとめ

  • Play-WS でWeb Serviceに接続できます�
  • WS利用の基礎知識5つ
    • Action, Future, Reads, Implict Parameter, Option�
  • Play2.2 の標準Jsonライブラリはマーシャル機能が強力で便利

29 of 31

ご清聴ありがとうございました。

30 of 31

参考(1)

  • Play! でつなげるWebサービス(XML-RPC/OAuth2.0)
    • http://shanon-tech.blogspot.jp/2012/02/play-webxml-rpcoauth20.html
  • scala.slick.SlickException
    • https://github.com/slick/slick/issues/625
  • アクション・コントロール・レスポンス
    • http://www.playframework-ja.org/documentation/2.1.5/ScalaActions
  • Play 2.1 移行ガイド(日本語)
    • http://www.playframework-ja.org/documentation/2.1.5/Migration

31 of 31

参考(2)

  • Playframeworkの WS API でHTTP GETする
    • http://bati11blog.hatenablog.com/entry/2013/05/25/220956
  • play.api.libs.json.Jsonを使ったJsonシリアライズ
    • http://qiita.com/NewGyu/items/b9762bfb63d8e9312792
  • ScalaのOptionステキさについてアツく語ってみる
    • http://yuroyoro.hatenablog.com/entry/20100710/1278763193