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; }
// 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); }
// 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"); } }
// 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"); } }
// 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); } }
// 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' ... } }
// 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
アノテーションをコンテナーリクエストフィルタークラスに追加します。Copy to Clipboard Copied! Toggle word wrap Toggle overflow // 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 { ... }
// 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
要素のプロバイダーリストにサーバー要求フィルターを追加します。Copy to Clipboard Copied! Toggle word wrap Toggle overflow <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>
<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
アノテーションのみです。しかし実際には、標準的なアプローチはやや柔軟性がなく、大規模なプロジェクトに多くのライブラリーが含まれている場合は、プロバイダーの衝突につながる可能性があります。