2.12. RESTEasy フィルターおよびインターセプター
Jakarta RESTful Web Services には、フィルターとインターセプターという 2 つの異なる概念があります。フィルターは主に、着信および発信要求ヘッダーまたは応答ヘッダーを変更または処理するために使用されます。リクエストおよび応答処理の前後に実行されます。
2.12.1. サーバー側フィルター リンクのコピーリンクがクリップボードにコピーされました!
サーバー側では、ContainerRequestFilters および ContainerResponseFilters という 2 種類のフィルターを利用できます。ContainerRequestFilters は、Jakarta RESTful Web Services リソースメソッドが呼び出される前に実行されます。ContainerResponseFilters は、Jakarta RESTful Web Services リソースメソッドが呼び出された後に実行されます。
さらに、ContainerRequestFilters には pre-matching および post-matching のタイプがあります。事前一致する ContainerRequestFilters が @PreMatching アノテーションで指定され、Jakarta RESTful Web Services リソースメソッドが受信 HTTP 要求に一致する前に実行されます。事後一致する ContainerRequestFilters は、@PostMatching アノテーションで指定され、Jakarta RESTful Web Services リソースメソッドが受信 HTTP 要求と照合された後に実行されます。
事前一致フィルターは多くの場合、要求属性を変更して、.xml をストリップし、Accept ヘッダーを追加するなど、特定のリソースメソッドへの一致方法を変更するために使用されます。ContainerRequestFilters は ContainerRequestContext.abortWith (Response) を呼び出して要求を中止できます。たとえば、フィルターはカスタム認証プロトコルを実装している場合に中止する必要があるかもしれません。
リソースクラスメソッド実行後、Jakarta RESTful Web Services はすべての 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. クライアント側のフィルター リンクのコピーリンクがクリップボードにコピーされました!
クライアント側のフィルターの詳細は、本書の Jakarta RESTful Web Services Client API のセクションを参照してください。
2.12.3. RESTEasy インターセプター リンクのコピーリンクがクリップボードにコピーされました!
2.12.3.1. Jakarta RESTful Web Services 呼び出しのインターセプト リンクのコピーリンクがクリップボードにコピーされました!
RESTEasy は Jakarta RESTful Web Services 呼び出しをインターセプトし、インターセプターと呼ばれるリスナーのようなオブジェクトを介してルーティングすることができます。
フィルターはリクエストまたは応答ヘッダーを変更しますが、インターセプターはメッセージボディーを処理します。インターセプターは、対応するリーダーまたはライターと同じ呼び出しスタックで実行されます。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 Jakarta RESTful Web Services インターセプターを登録するには、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 の展開をサポートするため、クライアントフレームワークまたは Jakarta RESTful Web Services サービスはメッセージボディーを 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) が含まれます。これは、デプロイされた各 Jakarta RESTful Web Services メソッドおよびデプロイされた各 Jakarta RESTful Web Services メソッドに対して呼び出されます。ResourceInfo パラメーターには、デプロイされている現在の Jakarta RESTful Web Services メソッドに関する情報が含まれています。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 の動作は、Jakarta Contexts and Dependency Injection インターセプターと似ています。@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 がクライアント側で出力されます。
サーバー側で、フィルターまたはインターセプターによって出力される例外は、Jakarta RESTful Web Services メソッドから出力された他の例外と同じ方法で処理されます。これは、出力される例外の ExceptionMapper を見つけようとします。Jakarta RESTful Web Services メソッドでの例外の処理方法は Exception Handling を参照してください。