Infinispan 6.0がけっこう大改造してそうな感じなので、またいろいろ変わるんだろうなぁと思いつつも、InfinispanのREST Serverで遊んでみました。
Infinispan REST Server
https://docs.jboss.org/author/display/ISPN/Infinispan+REST+Server
Accessing data in Infinispan via RESTful interface
https://docs.jboss.org/author/display/ISPN/Accessing+data+in+Infinispan+via+RESTful+interface
概ねここに書いてある通りで、
- HTTP PUT/POSTでデータの登録/更新
- HTTP GETでデータの取得
- HTTP DELETEでデータの削除
ができるRESTfulなインタフェースを備えた機能です。
では、試してみましょう…と言いたいところですが、まずはいろいろ準備する必要があります。
とりあえず、Infinispan Serverをダウンロードしてきまして、解凍。
$ unzip infinispan-server-5.3.0.Final-bin.zip
今回はクラスタリングをするつもりはないので、スタンドアロンで起動。
$ cd infinispan-server-5.3.0.Final/ $ bin/standalone.sh ========================================================================= JBoss Bootstrap Environment 〜省略〜 21:51:12,222 INFO [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss Infinispan Server 5.3.0.Final (AS 7.2.0.Final) started in 13441ms - Started 92 of 130 services (38 services are passive or on-demand)
この時点で、
http://localhost:8080/
にアクセスすると、RESTに関する説明が書かれたページが表示されます。
が、この後動かしてハマったのですが、よくよく読むとユーザを追加しなくちゃダメだよみたいなことが書いてあります。
とりあえず、Infinispan Serverは落としておきます。
22:02:28,853 INFO [org.jboss.as] (MSC service thread 1-4) JBAS015950: JBoss Infinispan Server 5.3.0.Final (AS 7.2.0.Final) stopped in 391ms
Ctrl-Cで。
で、ユーザの作り方は、Infinispanのドキュメントを見てみて…あれ?書いてありますっけ??
手順が書いてあったのは、Infinispanのブログでした。
Infinispan Server 5.3.0.Alpha1
http://infinispan.blogspot.jp/2013/04/infinispan-server-530alpha1.html
この手順通りにユーザを追加します。
$ bin/add-user.sh What type of user do you wish to add? a) Management User (mgmt-users.properties) b) Application User (application-users.properties)
どんなユーザを追加する?って聞かれているので、ここは
(a): b
で。次の質問は
Enter the details of the new user to add. Realm (ApplicationRealm) :
そのままEnter。次は、ユーザ名。ここでは、「username」とします。
Username : username
パスワード。アルファベットと数字と記号が入っていなくてはいけないみたいなので、「password$1」としました。
Password : Re-enter Password :
次に、ロールを聞かれるので「REST」と入力します。
What roles do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[ ]: REST
追加する内容が正しければ、yesと。
About to add user 'username' for realm 'ApplicationRealm' Is this correct yes/no? yes
ユーザの追加が表示されたパスに対して行われたことが表示されます。
Added user 'username' to file '/xxxxx/infinispan-server-5.3.0.Final/standalone/configuration/application-users.properties' Added user 'username' with roles REST to file '/xxxxx/infinispan-server-5.3.0.Final/standalone/configuration/application-roles.properties' Is this new user going to be used for one AS process to connect to another AS process? e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls. yes/no? no
最後の質問は、今回は他のサーバはいないので「no」で。
では、再びInfinispanを起動します。
$ bin/standalone.sh
続いてクライアント側ですが、せっかくREST Serverなので、クライアント側もそっち側のものを使うことにしました。
JAX-RSのクライアントを使います。実装は…最近よく聞くJerseyではなく、RESTEasyにしました。
RESTEasy
http://www.jboss.org/resteasy
JBoss系のプロジェクトですしね。
プログラム自体は、Scalaで記述。
build.sbt
name := "infinispan-restclient" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.2" organization := "littlewings" resolvers += "jboss" at "http://repository.jboss.org/nexus/content/groups/public/" libraryDependencies ++= Seq( "org.jboss.resteasy" % "resteasy-jaxrs" % "3.0.4.Final", "org.jboss.resteasy" % "resteasy-client" % "3.0.4.Final" )
Resolverを追加してますけど、JAX-RSのクライアントを使うだけなら、Resolverの追加は不要みたい??
JAX-RSクライアントを使った、Infinispanへのアクセスはこちらの資料を参考にしました。正確には、ここからリンクしてあるブログですが。
Infinispan REST API
http://www.slideshare.net/fribeiro1/infinispan-rest-api
こちらはBasic認証の部分はJerseyで書いてあったのですが、RESTEasyを使ったものに書き直しました。
書いたコードはこちら。
src/main/scala/InfinispanRestClient.scala
import javax.ws.rs.client.{ClientBuilder, ClientRequestFilter, Entity} import javax.ws.rs.core.{MediaType, Response} import org.jboss.resteasy.client.jaxrs.BasicAuthentication object InfinispanRestClient { def main(args: Array[String]): Unit = { val cacheName = "namedCache" val range = 1 to 10 val client = ClientBuilder.newBuilder.build client.register(new BasicAuthentication("username", "password$1"), classOf[ClientRequestFilter]) // データの登録/更新 range.foreach { i => val (key, entity) = (s"key$i", Entity.text(s"value$i")) val response = client .target(s"http://localhost:8080/rest/$cacheName/$key") .request .put(entity) // postでもOK println(s"Put $key Status => ${response.getStatus}") response.close() } // データの取得 // キーの存在確認だけなら、headを使うみたい range.foreach { i => val key = s"key$i" val response = client .target(s"http://localhost:8080/rest/$cacheName/$key") .request .get println(s"Get $key Status => ${response.getStatus}") println(s"Get Value Key[$key] = ${response.readEntity(classOf[String])}") response.close() } // 存在しないキーに対してアクセス { val key = "notFoundKey" val response = client .target(s"http://localhost:8080/rest/$cacheName/$key") .request .get // 存在しないキーに対しては、404が返る require(response.getStatus == Response.Status.NOT_FOUND.getStatusCode) response.close() } // 存在しないCacheに対してアクセス { val response = client .target(s"http://localhost:8080/rest/notExistCache") .request .get // 存在しないCacheに対しては、404が返る require(response.getStatus == Response.Status.NOT_FOUND.getStatusCode) response.close() } // キー一覧の取得 { val response = client .target(s"http://localhost:8080/rest/$cacheName") // .request(MediaType.TEXT_HTML_TYPE) // デフォルト .request(MediaType.APPLICATION_JSON_TYPE) // .request(MediaType.APPLICATION_XML_TYPE) // .request(MediaType.TEXT_PLAIN_TYPE) // .request(MediaType.WILDCARD_TYPE) // TEXT_HTML_TYPEと同じ .get println(s"Get $cacheName Status => ${response.getStatus}") println(s"Get $cacheName Value = ${response.readEntity(classOf[String])}") response.close() } // Cacheから特定キーのデータを削除 range.withFilter(_ % 2 == 0).foreach { i => val key = s"key$i" val response = client .target(s"http://localhost:8080/rest/$cacheName/$key") .request .delete println(s"Delete $key Status => ${response.getStatus}") response.close() } // 削除後のデータの確認 { val response = client .target(s"http://localhost:8080/rest/$cacheName") .request(MediaType.TEXT_PLAIN_TYPE) .get require(response.readEntity(classOf[String]).split("""\s+""").size == 5) response.close() } // Cacheの全データを削除 { val response = client .target(s"http://localhost:8080/rest/$cacheName") .request .delete println(s"Delete $cacheName Status => ${response.getStatus}") response.close() } // 削除後のデータの確認 { val response = client .target(s"http://localhost:8080/rest/$cacheName") .request(MediaType.TEXT_PLAIN_TYPE) .get require(response.readEntity(classOf[String]).isEmpty) response.close() } // Clientのクローズ client.close() } }
何気に、初JAX-RS。しかも、クライアントから(笑)。Basic認証の部分は若干てこずったというか、単にオブジェクトを渡すだけだと、Scalaでオーバーロードされたメソッドをうまく解決できませんでした…。
URL自体は
http://[ホスト名]:[ポート]/rest/[Cache名]/[キー]
となっていてわかりやすいですね。
今回は、デフォルトで用意されているCacheを使用しましたが、
standalone/configuration/standalone.xml
追加したければ
<subsystem xmlns="urn:infinispan:server:core:5.3" default-cache-container="local"> <cache-container name="local" default-cache="default"> <local-cache name="default" start="EAGER"> <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="memcachedCache" start="EAGER"> <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="namedCache" start="EAGER"/> </cache-container> <cache-container name="security"/> </subsystem>
の部分に加えてあげればOKです。
例えば、「restCache」を追加したければ
<cache-container name="local" default-cache="default"> <local-cache name="default" start="EAGER"> <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="memcachedCache" start="EAGER"> <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="namedCache" start="EAGER"/> <local-cache name="restCache" start="EAGER"/> </cache-container>
みたいにします。