Menu Close

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 インターフェース

ContainerRequestFilterfilter メソッドは、javax.ws.rs.container.ContainerRequestContext 型の引数を 1 つ受け取り、これは受信リクエストメッセージとその関連メタデータにアクセスするために使用できます。ContainerRequestContext インターフェースは以下のように定義されます。

// Java
...
package javax.ws.rs.container;

import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyWriter;

public interface ContainerResponseContext {

    public int getStatus();

    public void setStatus(int code);

    public Response.StatusType getStatusInfo();

    public void setStatusInfo(Response.StatusType statusInfo);

    public MultivaluedMap<String, Object> getHeaders();

    public abstract MultivaluedMap<String, String> getStringHeaders();

    public String getHeaderString(String name);

    public Set<String> getAllowedMethods();

    public Date getDate();

    public Locale getLanguage();

    public int getLength();

    public MediaType getMediaType();

    public Map<String, NewCookie> getCookies();

    public EntityTag getEntityTag();

    public Date getLastModified();

    public URI getLocation();

    public Set<Link> getLinks();

    boolean hasLink(String relation);

    public Link getLink(String relation);

    public Link.Builder getLinkBuilder(String relation);

    public boolean hasEntity();

    public Object getEntity();

    public Class<?> getEntityClass();

    public Type getEntityType();

    public void setEntity(final Object entity);

    public void setEntity(
            final Object entity,
            final Annotation[] annotations,
            final MediaType mediaType);

    public Annotation[] getEntityAnnotations();

    public OutputStream getEntityStream();

    public void setEntityStream(OutputStream outputStream);
}

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 ランタイムにインストールする) を バインド するには、以下の手順を実行します。

  1. 以下のコードフラグメントで示されるように、@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 アノテーション (スキャンフェーズ) の付いたクラスを検索します。

  2. 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 アノテーションのみです。しかし、実際には、標準的なアプローチはやや柔軟性が悪く、大規模なプロジェクトに多数のライブラリーが含まれている場合、プロバイダーがクラッシュする可能性があります。