ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

JAX-RS アプリケーションの 404 Not Found のカスタマイズ (リソースが見つからない場合)

JAX-RS アプリケーションでリソースが見つからない場合に表示される 404 Not Found のレスポンスの内容を変更したい場合にどうすればいいか、という話。

リソースが見つからない場合: NotFoundException が投げられる

まずは、そもそもの話として、リソースが見つからなかった場合に内部的にどういう処理が行われているのか見てみます。

JAX-RS 2.0 のドキュメントを見ると、3.7.2 節 「Request Matching」 において、リソースクラスやリソースメソッドの選択について書かれています。 そこには、リクエストにマッチするリソースクラスがなかった場合の処理として、次のように書かれていました。

(d) If E is empty then no matching resource can be found, the algorithm terminates and an implementation MUST generate a
NotFoundException (404 status) and no entity

マッチするリソースクラスが存在しない場合、NotFoundException が投げられるようです。 マッチするリソースメソッドがなかった場合も、同様に NotFoundException が投げられると書かれています。

例外をレスポンスにマッピングする: ExceptionMappter<T>

さて、内部的には例外が発生していることがわかりましたので、その例外をうまく扱いたいところです。

JAX-RS には、例外をレスポンスにマッピングする Exception Mapping Provider という仕組みがあります。 JAX-RS 2.0 のドキュメントの 4.4 節 「Exception Mapping Providers」 に次のように書かれています。

Exception mapping providers map a checked or runtime exception to an instance of
Response. An exception mapping provider implements the ExceptionMapper<T>
interface and may be annotated with @Provider for automatic discovery. When choosing an exception mapping provider to map an exception, an implementation MUST use the provider whose generic type is the nearest superclass of the exception

NotFoundException を適当なレスポンスにマッピングするための Exception Mappging Provider を書くことで、リソースが見つからない場合の 404 Not Found をカスタマイズできます。 (もちろん、リソースが見つからない場合のエラー以外にも適用できます。)

サンプルコード

カスタマイズ前

以下のように、JAX-RS 実装として Jersey を使用し、Grizzly 上で JAX-RS アプリケーションを動かしているとします。

// 必要な import 文
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

// 処理
URI baseUri = UriBuilder.fromUri("http://localhost/").port(10082).build();
ResourceConfig config = new ResourceConfig();
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, config);

上のコードでは、リソースクラスを 1 個も登録していないので、ブラウザで http://localhost:10082/ にアクセスしても 404 Not Found が返ってきます。 (ページ自体は真っ白。)

カスタマイズ後

以下のような Exception Mapping Provider を書いてみます。 単に 「404 Not Found!!!」 という文字列を表示するだけのレスポンスです。

import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {

    @Override
    public Response toResponse(NotFoundException exception) {
        return Response.status(Status.NOT_FOUND).entity("404 Not Found!!!").build();
    }

}

そして、以下のように ResourceConfig に登録します。

URI baseUri = UriBuilder.fromUri("http://localhost/").port(10082).build();
ResourceConfig config = new ResourceConfig();
config.register(NotFoundExceptionMapper.class);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, config);

これで、リソースが見つからなかった場合にカスタマイズされた 「404 Not Found!!!」 という文字列が返されるようになります。