2.12. RESTEasy フィルターおよびインターセプター

JAX-RS にはインターセプターにフィルターとインターセプターという 2 つの異なる概念があります。フィルターは主に、着信および発信要求ヘッダーまたは応答ヘッダーを変更または処理するために使用されます。リクエストおよび応答処理の前後に実行されます。

2.12.1. サーバー側フィルター

サーバー側では、ContainerRequestFilters および ContainerResponseFilters という 2 種類のフィルターを利用できます。ContainerRequestFilters は JAX-RS リソースメソッドが呼び出される前に実行されます。ContainerResponseFilters は JAX-RS リソースメソッドの呼び出し後に実行されます。

さらに、ContainerRequestFilters には pre-matching および post-matching のタイプがあります。事前一致する ContainerRequestFilters@PreMatching アノテーションで指定され、JAX-RS リソースメソッドが受信 HTTP リクエストに一致する前に実行されます。一致しない ContainerRequestFilters@PostMatching アノテーションで指定され、JAX-RS リソースメソッドが受信 HTTP リクエストに一致した後に実行されます。

事前一致フィルターは多くの場合、要求属性を変更して、.xml をストリップし、Accept ヘッダーを追加するなど、特定のリソースメソッドへの一致方法を変更するために使用されます。ContainerRequestFiltersContainerRequestContext.abortWith (Response) を呼び出して要求を中止できます。たとえば、フィルターはカスタム認証プロトコルを実装している場合に中止する必要があるかもしれません。

リソースクラスメソッドの実行後、JAX-RS はすべての ContainerResponseFilters を実行します。これらのフィルターにより、発信応答がマーシャリングされ、クライアントに送信される前に送信応答を変更できます。

例: 要求フィルター

public class RoleBasedSecurityFilter implements ContainerRequestFilter {
  protected String[] rolesAllowed;
  protected boolean denyAll;
  protected boolean permitAll;

  public RoleBasedSecurityFilter(String[] rolesAllowed, boolean denyAll, boolean permitAll) {
    this.rolesAllowed = rolesAllowed;
    this.denyAll = denyAll;
    this.permitAll = permitAll;
  }

  @Override
  public void filter(ContainerRequestContext requestContext) throws IOException  {
    if (denyAll) {
       requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build());
       return;
    }
    if (permitAll) return;
    if (rolesAllowed != null) {
       SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class);
       if (context != null) {
          for (String role : rolesAllowed) {
             if (context.isUserInRole(role)) return;
          }
          requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build());
          return;
       }
    }
    return;
  }
}

例: 応答フィルター

public class CacheControlFilter implements ContainerResponseFilter {
   private int maxAge;

   public CacheControlFilter(int maxAge) {
      this.maxAge = maxAge;
   }

   public void filter(ContainerRequestContext req, ContainerResponseContext res)
           throws IOException {
      if (req.getMethod().equals("GET")) {
         CacheControl cc = new CacheControl();
         cc.setMaxAge(this.maxAge);
         res.getHeaders().add("Cache-Control", cc);
      }
   }
}

2.12.2. クライアント側のフィルター

クライアント側のフィルターの詳細は、本ガイドの JAX-RS Client API のセクションを参照してください。

2.12.3. RESTEasy インターセプター

2.12.3.1. JAX-RS 呼び出しのインターセプト

RESTEasy は JAX-RS 呼び出しをインターセプトし、インターセプターと呼ばれるリスナーのようなオブジェクトでそれらをルーティングできます。

フィルターはリクエストまたは応答ヘッダーを変更しますが、インターセプターはメッセージボディーを処理します。インターセプターは、対応するリーダーまたはライターと同じ呼び出しスタックで実行されます。ReaderInterceptorsMessageBodyReaders の実行をラップします。WriterInterceptorsMessageBodyWriters の実行をラップします。これは、特定のコンテンツエンコーディングを実装するために使用できます。これらは、デジタル署名の生成や、マーシャリング前後の Java オブジェクトモデルの投稿または事前処理に使用することができます。

ReaderInterceptors および WriterInterceptors は、サーバー側またはクライアント側のいずれかで使用することができます。これらは、@ServerInterceptor または @ClientInterceptor のいずれかや、@Provider でアノテーションが付けられたため、RESTEasy はそれらをインターセプター一覧に追加するかどうかを認識できます。

これらのインターセプターは、MessageBodyReader.readFrom() または MessageBodyWriter.writeTo() の呼び出しをラップします。これらは、OutputInput ストリームをラップするために使用できます。

例: インターセプター

@Provider
public class BookReaderInterceptor implements ReaderInterceptor {
    @Inject private Logger log;
    @Override
    @ReaderInterceptorBinding
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
        log.info("*** Intercepting call in BookReaderInterceptor.aroundReadFrom()");
        VisitList.add(this);
        Object result = context.proceed();
        log.info("*** Back from intercepting call in BookReaderInterceptor.aroundReadFrom()"); return result;
    }
}

インターセプターと MessageBodyReader または Writer は、単一の大きな Java 呼び出しスタックで呼び出されます。ReaderInterceptorContext.proceed() または WriterInterceptorContext.proceed() は、次のインターセプターに移動するために呼び出されます。または、呼び出すインターセプターがない場合は、MessageBodyReaderMessageBodyWriterreadFrom()writeTo() メソッドになります。このラッピングにより、オブジェクトは ReaderWriter に到達する前に変更でき、proceed() が返された後にクリーンアップされます。

以下の例は、ヘッダー値を応答に追加するサーバー側のインターセプターです。

@Provider
public class BookWriterInterceptor implements WriterInterceptor {
   @Inject private Logger log;

   @Override
   @WriterInterceptorBinding
   public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
      log.info("*** Intercepting call in BookWriterInterceptor.aroundWriteTo()");
      VisitList.add(this);
      context.proceed();
      log.info("*** Back from intercepting call in BookWriterInterceptor.aroundWriteTo()");
   }
}

2.12.3.2. インターセプターの登録

アプリケーションで RESTEasy JAX-RS インターセプターを登録するには、context-param 要素の resteasy.providers パラメーターの下で web.xml ファイルにリストするか、これをクラスまたは Application.getClasses()Application.getSingletons() メソッドのオブジェクトとして返します。

<context-param>
    <param-name>resteasy.providers</param-name>
    <param-value>my.app.CustomInterceptor</paramvalue>
</context-param>
package org.jboss.resteasy.example;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class MyApp extends Application {

  public java.util.Set<java.lang.Class<?>> getClasses() {
    Set<Class<?>> resources = new HashSet<Class<?>>();
    resources.add(MyResource.class);
    resources.add(MyProvider.class);
    return resources;
  }
}
package org.jboss.resteasy.example;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class MyApp extends Application {

    protected Set<Object> singletons = new HashSet<Object>();

    public MyApp() {
        singletons.add(new MyResource());
        singletons.add(new MyProvider());
    }

    @Override
    public Set<Object> getSingletons() {
        return singletons;
    }
}

2.12.4. gzip 圧縮および展開

RESTEasy は GZIP 圧縮および展開に対応しています。GZIP 圧縮をサポートするため、クライアントフレームワークまたは JAX-RS サービスは gzipContent-Encoding とメッセージボディーを自動的に展開し、このヘッダーを手作業で設定する必要がないように Accept-Encoding ヘッダーを gzip, deflate に自動的に設定できます。GZIP 圧縮をサポートするため、クライアントフレームワークがリクエストを送信している場合や、サーバーが gzip に設定された Content-Encoding ヘッダーを使用して応答を送信している場合に、RESTEasy は出力メッセージを圧縮します。@org.jboss.resteasy.annotation.GZIP アノテーションを使用して Content-Encoding ヘッダーを設定できます。

以下の例は、gzip 圧縮する、出力メッセージボディーの order をタグ付けします。

例: GZIP 圧縮

@Path("/")
public interface MyProxy {

   @Consumes("application/xml")
   @PUT
   public void put(@GZIP Order order);
}

例: サーバー応答のタグ付け GZIP 圧縮

@Path("/")
public class MyService {

   @GET
   @Produces("application/xml")
   @GZIP
   public String getData() {...}
}

2.12.4.1. GZIP 圧縮および展開の設定

注記

RESTEasy は、サイズが大きいものの、攻撃者が圧縮し、サーバーに送信しているエンティティーの展開を防ぐために、GZIP 圧縮および展開をデフォルトで無効にします。

GZIP 圧縮と展開には、以下の 3 つのインターセプターが関係します。

  • org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor: Content-Encoding ヘッダーが存在し、値が gzip の場合、GZIPDecodingInterceptor はメッセージボディーを展開する InputStream をインストールします。
  • org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor: Content-Encoding ヘッダーが存在し、値が gzip の場合、GZIPEncodingInterceptor はメッセージボディーを圧縮する OutputStream をインストールします。
  • org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter: Accept-Encoding ヘッダーが存在しない場合、AcceptEncodingGZIPFiltergzip, deflate の値で Accept-Encoding ヘッダーを追加します。Accept-Encoding ヘッダーが存在するが gzip が含まれていない場合は、AcceptEncodingGZIPFilter インターセプターが値 , gzip を追加します。

    注記

    GZIP 圧縮または展開を有効にしても、AcceptEncodingGZIPFilter インターセプターの存在には依存しません。

GZIP デコンプレッシングを有効にすると、GZIPDecodingInterceptor が圧縮メッセージボディーから抽出できるバイト数の上限が設定されます。デフォルトの制限は 10,000,000 です。

2.12.4.2. サーバー側の GZIP 設定

インターセプターを有効にするには、クラスパスの javax.ws.rs.ext.Providers ファイルにクラス名を追加します。圧縮ファイルの上限は、web アプリケーションコンテキストパラメーター resteasy.gzip.max.input を使用して設定します。サーバー側でこの制限を超えると、GZIPDecodingInterceptor はステータス 413 - Request Entity Too Large の応答と、上限を指定するメッセージを返します。

2.12.4.2.1. クライアント側の GZIP 設定

GZIP インターセプターを有効にするには、ClientWebTarget などでこれを登録します。例を以下に示します。

Client client = new ResteasyClientBuilder() // Activate gzip compression on client:
    .register(AcceptEncodingGZIPFilter.class)
    .register(GZIPDecodingInterceptor.class)
    .register(GZIPEncodingInterceptor.class)
    .build();

圧縮ファイルの上限を設定するには、特定の値で GZIPDecodingInterceptor のインスタンスを作成します。

Client client = new ResteasyClientBuilder() // Activate gzip compression on client:
    .register(AcceptEncodingGZIPFilter.class)
    .register(new GZIPDecodingInterceptor(256))
    .register(GZIPEncodingInterceptor.class)
    .build();

クライアント側で上限を超えると、GZIPDecodingInterceptor は上限を指定するメッセージとともに ProcessingException を出力します。

2.12.5. リソースごとのメソッドフィルターとインターセプター

フィルターまたはインターセプターを特定のリソースメソッドに対してのみ実行したい場合があります。これは、以下の 2 つのの方法で実行できます。

DynamicFeature インターフェイスの実装

DynamicFeature インターフェイスには、コールバックメソッド configure(ResourceInfo resourceInfo, FeatureContext context) が含まれます。これは、デプロイされた各 JAX-RS メソッドに対して呼び出されます。ResourceInfo パラメーターには、デプロイされている現在の JAX-RS メソッドについての情報が含まれます。FeatureContext は、Configurable インターフェイスの拡張機能です。このパラメーターの register() メソッドを使用して、このメソッドに割り当てるフィルターとインターセプターをバインドできます。

例: DynamicFeature インターフェイスの使用

@Provider
public class AnimalTypeFeature implements DynamicFeature {
    @Override
    public void configure(ResourceInfo info, FeatureContext context) {
        if (info.getResourceMethod().getAnnotation(GET.class) != null)
            AnimalFilter filter = new AnimalFilter();
            context.register(filter);
        }
    }
}

上記の例では、AnimalTypeFeature を使用して登録するプロバイダーは、インターフェイスのいずれかを実装する必要があります。この例では、以下のインターフェイスのいずれかを実装する必要があるプロバイダー AnimalFilter を登録します。これは、ContainerRequestFilterContainerResponseFilterReaderInterceptorWriterInterceptor、または Feature のいずれかを実装する必要があります。この場合、AnimalFilter は GET アノテーションが付けられたすべてのリソースメソッドに適用されます。詳細は DynamicFeature Documentation を参照してください。

@NameBinding アノテーションを使用します。

@NameBinding は CDI インターセプターのように機能します。@NameBinding でカスタムアノテーションにアノテーションを付け、そのカスタムアノテーションをフィルターおよびリソースメソッドに適用します。

例: @NameBinding の使用

@NameBinding
public @interface DoIt {}

@DoIt
public class MyFilter implements ContainerRequestFilter {...}

@Path("/root")
public class MyResource {

   @GET
   @DoIt
   public String get() {...}
}

詳細は NameBinding Documentation のドキュメントを参照してください。

2.12.6. 順序付け

順序付けは、フィルターまたはインターセプタークラスの @Priority アノテーションを使用して実行されます。

2.12.7. フィルターおよびインターセプターによる例外処理

フィルターまたはインターセプターに関連する例外は、クライアント側またはサーバー側で発生する可能性があります。クライアント側では、javax.ws.rs.client.ProcessingException および javax.ws.rs.client.ResponseProcessingException という例外を処理する必要があります。javax.ws.rs.client.ProcessingException は、リクエストがサーバーに送信される前にエラーが発生していた場合、クライアント側で出力されます。サーバーからクライアントが受信した応答の処理にエラーが発生すると、javax.ws.rs.client.ResponseProcessingException がクライアント側で出力されます。

サーバー側では、フィルターまたはインターセプターによって発生する例外は JAX-RS メソッドから発生する他の例外と同じ方法で処理されます。これは、出力される例外の ExceptionMapper を検索します。JAX-RS メソッドにおける例外の処理方法の詳細は、Exception Handling セクションを参照してください。