JAX-RS 2.0 (JSR 339)の使い心地について調べたのでメモ。
実装:
- Jersey
- Apache CXF -- Index
- RESTEasy - JBoss Community
雑多なトピック
JAX-RSを一般的なWebアプリケーションのためのフレームワークとしてはどう評価されているのか?
- JAX-RSはHTML Webアプリケーションを開発するのに充分なフレームワークであるか? - AOEの日記
- JAX-RSとかの話 — 裏紙
結論:まずます良さげ。
JAX-RS関連調べてて気になったトピック
- Bean Validation: Bean Validation
- 私のBeanValidationの使い方(Java EE Advent Calendar 2013) — 裏紙
- JAX-RSでパラメータの受け取り方をいろいろ試す — 裏紙
- Oracle Blogs 日本語のまとめ: [Java] Java EE 7 and JAX-RS 2.0
- Project Grizzly - Http Server Framework Overview
- Grizzly 2.0: HttpServer API. Implementing simple HTTP server (Mytec)
JAX-RSでのユーザ認証について
Servletコンテナで提供しているRoleベースの認証機能を使う。JAX-RSでは"@Context"アノテーションでSecurity Contextにアクセスできるよう定められているため、Servletコンテナ側でBasic認証など設定していれば、FilterなどのProviderや、リソースから"@Context"経由で参照し、処理出来る。
- A simple JAX-RS security context example in GlassFish | IT Business Tonic
- Jersey (JAX-RS) のアクセス制御 - あめだま
- forcing the Jersey Grizzly server to authenicate - Stack Overflow
- Jersey (JAX-RS) implements a HTTP Basic Auth decoder | Simple API
→結論は↓のトピックに持ち越し
RESTfulなAPIサービスと、HTTPセッションについて
- Jersey - In JAX-RS: Jersey: API model, how do you access HTTP session variables
以下、RESTfulなAPIサービスで認証をどう実装すればよいのか、Stack Overflowでも賑わってました:
- web services - How Can I Retrieve The Session Id from a Jax RS Webservice? - Stack Overflow
- jersey - HttpSession is null in RESTful web service? - Stack Overflow
- Authentication for REST web services - Stack Overflow
- methods - How do I authenticate user in REST web service? - Stack Overflow
→結論は↓のトピックに再度持ち越し
RESTとステートレス性についてのそもそもの話
RESTではスケーラビリティ等を考慮して、ステートレスにしよう、という思想がある。そこで、CookieによるセッションIDで認証状態を管理してしまうと、サーバ側でのスケーラビリティで色々制約や工夫の必要が生じてしまうので、なるべくクライアント側だけでステートを持たせようね、という話があるようです。
- Representational State Transfer (REST) : 5.1.3 Stateless
- Experience and Evaluation : 6.3.4.2 Cookies
- RESTアンチパターン
- Learn REST: A Tutorial: 14.3. How do I handle authentication in REST?
結論・・・というかとりあえずの検討結果:
- ステートレス性を犠牲にしてスケーラビリティで苦労するのを受け入れた上で、普通にServletのCookieによるセッション管理機能をフィルタとかで組み込む方向性はアリだと思いました。
- Ajaxを使ったRIAで、AjaxだけBasic認証というのはかなり辛いと思いますので、OAuth2.0系を使ってtokenをクライアント側で管理してGET以外のパラメータでやりとりするのもありかなと。GETでやりとりしてしまうとプロキシやサーバログとかに残ってセキュリティ上問題になるので、GET禁止・・・となるとまたRESTの特性の一部が失われますが。
- Railsがやってるように、Client側のCookieの中にセッション状態を詰め込むのもアリだと思いますが、これはもう言語やプラットフォームごとにこのアプローチの得手不得手が出てきてしまいます。うかつに独自実装すると、セキュリティ上穴が出来てしまわないか怖いですし・・・。
- どちらにしても、原理主義でいくか、現場主義でいくか、バランス取らないとやってけないと思いました。
JAX-RS 2.0のフィルタ機能の参考
- Chapter 9. Filters and Interceptors
- JAX-RS のリファレンス実装 Jersey のフィルタを使う方法 - あめだま
- JAX-RS のリファレンス実装 Jersey のフィルタを使う方法 その2 - あめだま
- WebAPIのステートレスなCSRF対策 - あめだま
フィルタ中から、ContainerRequestContext#setProperty()経由でセットしたオブジェクトを、リソースクラスから読み出せるか?
- java - JBoss / Resteasy how to prepopulate objects using a filter before the exposed API? - Stack Overflow
- java - How I can add parameter to request from jersey filter request (ContainerRequestFilter) - Stack Overflow
- Read request attribute in a Jersey ContainerRequestFilter - Stack Overflow
結論:JAX-RS 2.0としての仕様ではそうしたアクセス用APIは(多分)提供されてない(っぽい)。
一応、Servletアプリ上で動かす場合はContainerRequestContext#setProperty()/getProperty()は ServletRequest の Attribute と連動しているよう定められているため、リソースクラスに "@Context" で HttpServletRequest をインジェクションして、そこから取り出す方法はある。
なお、JAX-RSのApplicationクラスが提供しているgetProperties()は、JAX-RSアプリ全体のconfigurationだったり、Servlet実装の場合は<context-param>や<init-param>と連動してたりするため、フィルタ機能とは関係ない。ただし、アプリ全体の設定値を取得する際には活用できそう。
Jersey使った時に、アプリ全体の初期化処理用のフックとかあるかな?(Servletのinitみたいな)
- java - Initialize database on Jersey webapp startup - Stack Overflow
- Jersey app-run initialization code on startup to initialize application - Stack Overflow
- Jersey - Servlet.Init() For Jersey REST Service
結論:仕様としては、無い。
ただし、例えばFilterはライフサイクル上、ひとつのアプリで1インスタンスだけなので、コンストラクタでグローバルな設定を初期化するのはアリだろう。
また、Servletコンテナ上で動かすのであれば、普通にServletのinit中で、Singletonで初期化するのもアリ。Servlet中のClassLoader上で動作するのであれば、JAX-RSアプリ内からも参照できるはず。Singletonを上手く使うのもちょっと色々工夫がいるだろうけど・・・。
あとはSpringやGuiceなどのDIコンテナと上手く組み合わせて工夫するとか。
エラー・例外のカスタマイズ
- 6.3. WebApplicationException and Mapping Exceptions to Responses
- JAX-RS アプリケーションの 404 Not Found のカスタマイズ (リソースが見つからない場合) - ひだまりソケットは壊れない
- java - JAX-RS / Jersey how to customize error handling? - Stack Overflow
リソースクラスの処理内で発生した例外についてはこんな感じかな?
- 自力でtry-catchした後に、カスタマイズした専用のWebApplicationException派生の例外クラスをthrowする。
- カスタマイズしたException派生の例外クラスをthrowし、それに対応するExceptionMapperをProviderとして用意しておく。
- カスタマイズしたException派生の例外クラスをthrowし、実行環境(Servletコンテナなど)のデフォルトの例外処理に委譲する。
また、JAX-RS側のデフォルトの404例外などをカスタマイズしたい場合は、内部的に以下のような例外が定められていてthrowされるので、それに対応するExceptionMapperを自分たちで準備しておけば良いっぽい。(単にJavaDocからそれっぽいのを引っ張ってきてるだけのため、本当にそうなのかは不明。)
- NoContentException
- NotAcceptableException
- NotAllowedException
- NotAuthorizedException
- NotFoundException
- NotSupportedException
こういうのがあるのを知っておけば、カスタム例外を作る手間が省けて、JAX-RS側で用意してくれてるこれらの例外で済ませられるケースもあるかも?
レスポンスHTTPヘッダーやボディのカスタマイズ
- Chapter 6. Representations and Responses
- jax rs - Is it possible to control the filename for a Response from a Jersey Rest service? - Stack Overflow
- my kingdom for a smile :-): How to return a Location header from a Jersey REST service
結論としては、HTTPレスポンスのカスタマイズ性は非常に自由度が高い状態にある。
まず、Response#status()でステータスコードを指定してResponseBuilderのインスタンスを取得する。
ResponseBuilderには、以下のようにレスポンスをカスタマイズできるメソッドが公開されている。
- entity() : byte[]やStringやObjectなど、レスポンスBODYを設定出来る。
- header() : レスポンスヘッダをカスタマイズ出来る。
- type() : Content-Typeヘッダをカスタマイズできる。"text/html"や"application/octet-stream"など頻用される値についてはMediaTYpeのstaticインスタンスとして定義済みなので、いちいち正確な綴りを思い出す必要がない。"charset"指定も、自然な形でメソッドとして統合されている。
- location() : 基本的にリダイレクト系は、Response#seeOther(URI), temporaryRedirect(URI), created(URI) でステータスコードも一緒に設定してくれるが、フルカスタマイズで個別にLocationヘッダーを設定したい場合はこちらのメソッドが使える。
- encoding() : ガラケーサイト向けにSJISで出力したい、という場合に、entity()にはJava世界の文字列をそのままStringで渡して、こちらのencoding()で実際に出力するときのエンコーディングを指定する。Content-Typeのcharsetが自動的に連動してくれるかは不明なので、自分でtype()で指定しておくのが無難か。
上記メソッドは、いずれもそのメソッドで更新されたResponseBuilder()を返してくるので、メソッドコールのチェインをつなげていく格好になり、最後に"build()"メソッドを呼べば、最終的なResponseクラスのインスタンスが返される。これを単純にreturnすればよい。
※"@Context"でHttpServletResponseをインジェクとしてもらって、それも合わせて操作した場合に、何か衝突など発生しないか、については未検証。セッション管理系のCookie操作とか大丈夫かな?
プレーンテキスト形式でダウンロード
コメント