61.2. コンテナー要求フィルター
概要
本セクションでは、コンテナー要求フィルターを実装して登録する方法を説明します。このフィルターは、サーバー (コンテナー) 側の受信要求メッセージをインターセプトするために使用されます。コンテナー要求フィルターは、サーバー側でヘッダーを処理するために使用されることが多く、あらゆる種類の汎用要求処理 (つまり、呼び出された特定のリソースメソッドから独立している処理) に使用できます。
さらに、コンテナーリクエストフィルターは、PreMatchContainerRequest
(リソース一致ステップの前) と ContainerRequest
(リソース一致ステップの後) の 2 つの異なる拡張ポイントにインストールできるため、特別なケースになります。
ContainerRequestFilter インターフェイス
javax.ws.rs.container.ContainerRequestFilter
インターフェイスは以下のように定義されます。
// Java ... package javax.ws.rs.container; import java.io.IOException; public interface ContainerRequestFilter { public void filter(ContainerRequestContext requestContext) throws IOException; }
ContainerRequestFilter
インターフェイスを実装することで、サーバー側で以下のエクステンションポイントのいずれかにフィルターを作成できます。
-
PreMatchContainerRequest
-
ContainerRequest
ContainerRequestContext インターフェイス
ContainerRequestFilter
の filter
メソッドは、javax.ws.rs.container.ContainerRequestContext
型の引数を 1 つ受け取り、これは受信リクエストメッセージとその関連メタデータにアクセスするために使用できます。ContainerRequestContext
インターフェイスは以下のように定義されます。
// Java ... package javax.ws.rs.container; import java.io.InputStream; import java.net.URI; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; public interface ContainerRequestContext { public Object getProperty(String name); public Collection getPropertyNames(); public void setProperty(String name, Object object); public void removeProperty(String name); public UriInfo getUriInfo(); public void setRequestUri(URI requestUri); public void setRequestUri(URI baseUri, URI requestUri); public Request getRequest(); public String getMethod(); public void setMethod(String method); public MultivaluedMap getHeaders(); public String getHeaderString(String name); public Date getDate(); public Locale getLanguage(); public int getLength(); public MediaType getMediaType(); public List getAcceptableMediaTypes(); public List getAcceptableLanguages(); public Map getCookies(); public boolean hasEntity(); public InputStream getEntityStream(); public void setEntityStream(InputStream input); public SecurityContext getSecurityContext(); public void setSecurityContext(SecurityContext context); public void abortWith(Response response); }
PreMatchContainerRequest フィルターの実装例
PreMatchContainerRequest
エクステンションポイントのコンテナー要求フィルター (つまり、リソース一致の前にフィルターが実行される場合) を実装するには、ContainerRequestFilter
インターフェイスを実装するクラスを定義し、クラスに @PreMatching
アノテーションを付けます (PreMatchContainerRequest
拡張ポイントを選択するため)。
たとえば、以下のコードは、PreMatchContainerRequest
エクステンションポイントにインストールされる単純なコンテナーリクエストフィルターの例を示しています。ここでは、優先度は 20 です。
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.annotation.Priority; import javax.ws.rs.ext.Provider; @PreMatching @Priority(value = 20) @Provider public class SamplePreMatchContainerRequestFilter implements ContainerRequestFilter { public SamplePreMatchContainerRequestFilter() { System.out.println("SamplePreMatchContainerRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { System.out.println("SamplePreMatchContainerRequestFilter.filter() invoked"); } }
ContainerRequest フィルターの実装例
ContainerRequest
エクステンションポイント (つまり、リソース一致の 後 にフィルターが実行される場合) のコンテナー要求フィルターを実装するには、@PreMatching
アノテーション なし で ContainerRequestFilter
インターフェイスを実装するクラスを定義します。
たとえば、以下のコードは、ContainerRequest
エクステンションポイントにインストールされる単純なコンテナーリクエストフィルターの例を示しています。ここで、優先度は 30 になります。
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { public SampleContainerRequestFilter() { System.out.println("SampleContainerRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { System.out.println("SampleContainerRequestFilter.filter() invoked"); } }
ResourceInfo の注入
ContainerRequest
エクステンションポイント (つまりリソース一致発生 後) では、ResourceInfo
クラスを注入することで、一致したリソースクラスとリソースメソッドにアクセスできます。たとえば、以下のコードは、ResourceInfo
クラスを ContainerRequestFilter
クラスのフィールドとしてインジェクトする方法を示しています。
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; import javax.ws.rs.core.Context; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { @Context private ResourceInfo resinfo; public SampleContainerRequestFilter() { ... } @Override public void filter(ContainerRequestContext requestContext) { String resourceClass = resinfo.getResourceClass().getName(); String methodName = resinfo.getResourceMethod().getName(); System.out.println("REST invocation bound to resource class: " + resourceClass); System.out.println("REST invocation bound to resource method: " + methodName); } }
呼び出しの中止
コンテナー要求フィルターに適した実装を作成して、サーバー側の呼び出しを中止できます。通常、認証機能や承認機能を実装する場合など、これはサーバー側でセキュリティー機能を実装するのに役立ちます。受信要求が認証に失敗した場合には、コンテナー要求フィルター内から呼び出しを中止できます。
たとえば、以下の事前照合機能は URI のクエリーパラメーターからユーザー名とパスワードを抽出し、認証メソッドを呼び出してユーザー名とパスワードの認証情報を確認します。認証に失敗すると、ContainerRequestContext
オブジェクトで abortWith
を呼び出すことで呼び出しが中断され、クライアントに返されるエラー応答を渡します。
// Java package org.jboss.fuse.example; import javax.annotation.Priority; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; @PreMatching @Priority(value = 20) @Provider public class SampleAuthenticationRequestFilter implements ContainerRequestFilter { public SampleAuthenticationRequestFilter() { System.out.println("SampleAuthenticationRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { ResponseBuilder responseBuilder = null; Response response = null; String userName = requestContext.getUriInfo().getQueryParameters().getFirst("UserName"); String password = requestContext.getUriInfo().getQueryParameters().getFirst("Password"); if (authenticate(userName, password) == false) { responseBuilder = Response.serverError(); response = responseBuilder.status(Status.BAD_REQUEST).build(); requestContext.abortWith(response); } } public boolean authenticate(String userName, String password) { // Perform authentication of 'user' ... } }
サーバー要求フィルターのバインド
サーバー要求フィルターを バインド する (Apache CXF ランタイムにインストールする) には、以下の手順を実行します。
以下のコードフラグメントで示されるように、
@Provider
アノテーションをコンテナーリクエストフィルタークラスに追加します。// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { ... }
コンテナーリクエストフィルター実装が Apache CXF ランタイムにロードされると、REST 実装はロードされたクラスを自動的にスキャンし、
@Provider
アノテーション (スキャンフェーズ) の付いたクラスを検索します。XML で JAX-RS サーバーエンドポイントを定義する場合 (例: 「JAX-RS サーバーエンドポイントの設定」)、
jaxrs:providers
要素のプロバイダーリストにサーバー要求フィルターを追加します。<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs" xmlns:cxf="http://cxf.apache.org/blueprint/core" ... > ... <jaxrs:server id="customerService" address="/customers"> ... <jaxrs:providers> <ref bean="filterProvider" /> </jaxrs:providers> <bean id="filterProvider" class="org.jboss.fuse.example.SampleContainerRequestFilter"/> </jaxrs:server> </blueprint>
注記この手順は、Apache CXF の非標準要件です。厳密に言うと、JAX-RS 標準によれば、フィルターをバインドするために必要なのは
@Provider
アノテーションのみです。しかし実際には、標準的なアプローチはやや柔軟性がなく、大規模なプロジェクトに多くのライブラリーが含まれている場合は、プロバイダーの衝突につながる可能性があります。