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
ヘッダーを追加するなど、特定のリソースメソッドへの一致方法を変更するために使用されます。ContainerRequestFilters
は ContainerRequestContext.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 呼び出しをインターセプトし、インターセプターと呼ばれるリスナーのようなオブジェクトでそれらをルーティングできます。
フィルターはリクエストまたは応答ヘッダーを変更しますが、インターセプターはメッセージボディーを処理します。インターセプターは、対応するリーダーまたはライターと同じ呼び出しスタックで実行されます。ReaderInterceptors
は MessageBodyReaders
の実行をラップします。WriterInterceptors
は MessageBodyWriters
の実行をラップします。これは、特定のコンテンツエンコーディングを実装するために使用できます。これらは、デジタル署名の生成や、マーシャリング前後の Java オブジェクトモデルの投稿または事前処理に使用することができます。
ReaderInterceptors
および WriterInterceptors
は、サーバー側またはクライアント側のいずれかで使用することができます。これらは、@ServerInterceptor
または @ClientInterceptor
のいずれかや、@Provider
でアノテーションが付けられたため、RESTEasy はそれらをインターセプター一覧に追加するかどうかを認識できます。
これらのインターセプターは、MessageBodyReader.readFrom()
または MessageBodyWriter.writeTo()
の呼び出しをラップします。これらは、Output
や Input
ストリームをラップするために使用できます。
例: インターセプター
@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()
は、次のインターセプターに移動するために呼び出されます。または、呼び出すインターセプターがない場合は、MessageBodyReader
や MessageBodyWriter
の readFrom()
や writeTo()
メソッドになります。このラッピングにより、オブジェクトは Reader
や Writer
に到達する前に変更でき、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 サービスは gzip
の Content-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
ヘッダーが存在しない場合、AcceptEncodingGZIPFilter
はgzip, 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 インターセプターを有効にするには、Client
や WebTarget
などでこれを登録します。例を以下に示します。
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
を登録します。これは、ContainerRequestFilter
、ContainerResponseFilter
、ReaderInterceptor
、WriterInterceptor
、または 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 セクションを参照してください。