web-dev-qa-db-ja.com

JAX-RS / Jerseyどのようにエラー処理をカスタマイズしますか?

Jerseyを使用してJAX-RS(別名、JSR-311)を学習しています。ルートリソースを正常に作成し、パラメーターをいじっています。

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

これはうまく機能し、Date(String)コンストラクターが理解する現在のロケールの形式(YYYY/mm/ddやmm/dd/YYYYなど)を処理します。しかし、無効な値または理解できない値を指定すると、404応答が返されます。

例えば:

GET /hello?name=Mark&birthDate=X

404 Not Found

この動作をカスタマイズするにはどうすればよいですか?おそらく別の応答コード(おそらく「400 Bad Request」)?エラーのログはどうですか?トラブルシューティングを支援するために、カスタムヘッダーに問題の説明(「無効な日付形式」)を追加することはできますか?または、5xxステータスコードとともに、詳細なエラー応答全体を返しますか?

208
Mark Renouf

JAX-RSでエラー処理動作をカスタマイズするには、いくつかのアプローチがあります。簡単な方法を3つ紹介します。

最初のアプローチは、WebApplicationExceptionを拡張するExceptionクラスを作成することです。

例:

public class NotAuthorizedException extends WebApplicationException {
     public NotAuthorizedException(String message) {
         super(Response.status(Response.Status.UNAUTHORIZED)
             .entity(message).type(MediaType.TEXT_PLAIN).build());
     }
}

そして、この新しく作成された例外をスローするには、単純に:

@Path("accounts/{accountId}/")
    public Item getItem(@PathParam("accountId") String accountId) {
       // An unauthorized user tries to enter
       throw new NotAuthorizedException("You Don't Have Permission");
}

WebApplicationExceptionは実行時例外であるため、throws句で例外を宣言する必要はありません。これにより、クライアントに401応答が返されます。

2番目の簡単な方法は、コード内でWebApplicationExceptionのインスタンスを直接構築することです。このアプローチは、独自のアプリケーション例外を実装する必要がない限り機能します。

例:

@Path("accounts/{accountId}/")
public Item getItem(@PathParam("accountId") String accountId) {
   // An unauthorized user tries to enter
   throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}

このコードも401をクライアントに返します。

もちろん、これは単純な例です。必要に応じて例外をより複雑にすることができ、必要なHTTP応答コードを生成できます。

もう1つの方法は、既存の例外、おそらく@Providerアノテーションが付けられたExceptionMapperインターフェースを実装する小さなラッパークラスを持つObjectNotFoundExceptionをラップすることです。これは、JAX-RSランタイムに、ラップされたExceptionが発生した場合、ExceptionMapperで定義された応答コードを返すことを伝えます。

265
Steven Levine
@Provider
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> {

public Response toResponse(NotFoundException exception){

    return Response.status(Response.Status.NOT_FOUND).
    entity(new ErrorResponse(exception.getClass().toString(),
                exception.getMessage()) ).
    build();
}
}

上記のクラスを作成します。これは404(NotFoundException)を処理し、ここでtoResponseメソッドでカスタム応答を提供できます。同様に、カスタマイズされた応答を提供するためにマッピングする必要があるParamExceptionなどがあります。

66
Arnav

Jerseyは、パラメーターの非整列化に失敗するとcom.Sun.jersey.api.ParamExceptionをスローするため、1つの解決策は、これらのタイプの例外を処理するExceptionMapperを作成することです。

@Provider
public class ParamExceptionMapper implements ExceptionMapper<ParamException> {
    @Override
    public Response toResponse(ParamException exception) {
        return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build();
    }
}
34
Jan Kronquist

QueryParamアノテーション付き変数の再利用可能なクラスを記述することもできます

public class DateParam {
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(in));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

次のように使用します:

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

この場合、エラー処理は簡単です(400応答をスローします)が、このクラスを使用すると、ロギングなどを含む一般的なパラメーター処理を除外できます。

26

明らかな解決策の1つは、文字列を取得し、自分で日付に変換することです。そのようにして、必要な形式を定義し、例外をキャッチし、送信されるエラーを再スローまたはカスタマイズできます。構文解析の場合、SimpleDateFormatは正常に機能するはずです。

データ型のハンドラーをフックする方法もあると確信していますが、この場合に必要なのは簡単なコードだけです。

11
StaxMan

私も StaxMan が好きで、おそらくそのQueryParamをStringとして実装し、必要に応じて再スローして変換を処理します。

ロケール固有の動作が望ましい動作であり、期待される動作である場合、次を使用して400 BAD REQUESTエラーを返します。

throw new WebApplicationException(Response.Status.BAD_REQUEST);

その他のオプションについては、 javax.ws.rs.core.Response.Status のJavaDocを参照してください。

7
dshaw

@QueryParamドキュメントは言う

"注釈付きパラメータ、フィールド、またはプロパティのタイプTは、次のいずれかでなければなりません。

1)プリミティブ型であること
2)単一の文字列引数を受け入れるコンストラクタを持っている
3)単一のString引数を受け入れるvalueOfまたはfromStringという名前の静的メソッドを持っている(たとえば、Integer.valueOf(String)を参照)
4)javax.ws.rs.ext.ParamConverterProviderのJAX-RS拡張SPIの登録された実装を持ち、 "from型の文字列変換。
5)List、Set、またはSortedSetで、Tは上記の2、3、または4を満たします。結果のコレクションは読み取り専用です。 」

文字列形式のクエリパラメーターをT型に変換できない場合にユーザーにどの応答を送信するかを制御する場合は、WebApplicationExceptionをスローできます。 Dropwizardには、必要に応じて使用できる次の* Paramクラスが付属しています。

BooleanParam、DateTimeParam、IntParam、LongParam、LocalDateParam、NonEmptyStringParam、UUIDParam。 https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/Java/io/dropwizard/jersey/params を参照してください

Joda DateTimeが必要な場合は、Dropwizard DateTimeParam を使用してください。

上記のリストがニーズに合わない場合は、AbstractParamを拡張して独自のリストを定義します。解析メソッドをオーバーライドします。エラー応答本文を制御する必要がある場合は、エラーメソッドをオーバーライドします。

これに関するCoda Haleの良い記事は http://codahale.com/what-makes-jersey-interesting-parameter-classes/ にあります。

import io.dropwizard.jersey.params.AbstractParam;

import Java.util.Date;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class DateParam extends AbstractParam<Date> {

    public DateParam(String input) {
        super(input);
    }

    @Override
    protected Date parse(String input) throws Exception {
        return new Date(input);
    }

    @Override
    protected Response error(String input, Exception e) {
        // customize response body if you like here by specifying entity
        return Response.status(Status.BAD_REQUEST).build();
    }
}

Date(String arg)コンストラクターは非推奨です。 Java 8を使用している場合は、Java 8つの日付クラスを使用します。それ以外の場合は、joda date timeをお勧めします。

4
Srikanth

これは実際に正しい動作です。 Jerseyは、入力のハンドラーを見つけようとし、提供された入力からオブジェクトを構築しようとします。この場合、コンストラクタに提供された値Xで新しいDateオブジェクトを作成しようとします。これは無効な日付なので、慣例により、Jerseyは404を返します。

あなたができることは、文字列として書き換えて生年月日を入れてから解析しようとすることです。あなたが望むものを取得できない場合は、例外マッピングメカニズムのいずれかで必要な例外を自由に投げることができます(いくつかあります)。

1
ACV

ブラウザのログインウィンドウを開きたい場合の、@ Steven Lavineの拡張機能としての回答と同じです。ユーザーがまだ認証されていない場合、フィルターから適切に応答( MDN HTTP認証 )を返すことが難しいことがわかりました

これは、ブラウザーのログインを強制するための応答を作成するのに役立ちました。ヘッダーの追加の変更に注意してください。これにより、ステータスコードが401に設定され、ブラウザーがユーザー名/パスワードダイアログを開くヘッダーが設定されます。

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }
0
Omnibyte