1. 概要

このチュートリアルでは、 PlayFrameworkでのデフォルトのエラー処理を見ていきます。 さらに、カスタムエラーハンドラーを実装します。

まず、さまざまなエラーを返すPlayコントローラーを定義して、エラーが自動的に処理される方法を確認します。 次に、カスタムエラーハンドラーを提供し、その制限を確認します。

2. PlayFrameworkエラー

Playコントローラーは、次の3つの場合にエラーを返します。

  • 指定されたリクエストは無効であり、検証に失敗します(クライアント側のエラー)
  • アプリケーションコードが例外をスローする(サーバー側エラー)
  • Scalaコードでエラー応答を明示的に返します(ドメイン固有のエラー)

3. コントローラエラー

これら3つのエラーすべてを返すコントローラークラスを準備して、何が起こるかを確認し、エラーハンドラーを試してみましょう。

まず、さまざまな種類のエラーを返す5つのメソッドを使用して新しいコントローラークラスを定義します。

class ErrorDemoController @Inject()(
  val controllerComponents: ControllerComponents
) extends BaseController {
  def notFound(): Action[AnyContent] = Action {
    NotFound
  }
  def exception(): Action[AnyContent] = Action {
    throw new RuntimeException("Pretend that we have an application error.")
    Ok // We add this line just to make the returned type match the expected type
  }

  def internalError(): Action[AnyContent] = Action {
    InternalServerError
  }

  def badRequest(): Action[AnyContent] = Action {
    BadRequest
  }
}

コントローラを定義した後、新しいメソッドをルートハンドラとしてルートファイルに追加します。

GET     /errors/notfound             controllers.ErrorDemoController.notfound()
GET     /errors/exception           controllers.ErrorDemoController.exception()
GET     /errors/internalerror       controllers.ErrorDemoController.internalError()
GET     /errors/badRequest          controllers.ErrorDemoController.badRequest()

4. デフォルトのエラー処理

それでは、エンドポイントに1つずつアクセスして、何が起こるかを見てみましょう。 最初に、PlayFrameworkがアプリケーションエラーを処理する方法を示します。

4.1. Scala例外の処理

ブラウザでhttp:// localhost:9000 / errors/exceptionを開きましょう。 コードでは、アプリケーションがRuntimeExceptionをスローして、アプリケーションコードの障害をシミュレートしていることがわかります。 Play Frameworkを開発モードで実行すると、アプリケーションコードを含むエラーページが返され、問題の原因となった行が強調表示されます。

もちろん、アプリケーションコードをユーザーに表示することは、許容できないセキュリティリスクであるため、本番モードでは、エラーページに含まれる情報は少なくなります。

エラーメッセージで約束されているように、アプリケーションログを見ると、エラーIDとスタックトレースを含む行が表示されます。

! @7ibgcn351 - Internal server error, for (GET) [/errors/exception] ->
 
play.api.UnexpectedException: Unexpected exception[RuntimeException: Pretend that we have an application error.]
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:355)
    ...
Caused by: java.lang.RuntimeException: Pretend that we have an application error.
    at controllers.ErrorDemoController.$anonfun$exception$1(ErrorDemoController.scala:13)

4.2. InternalServerError NotFound 、およびBadRequestを明示的に返す

ブラウザでhttp:// localhost:9000 / errors / internalerror URLを開くと、 アプリケーションコードは、内部サーバーエラーを示すステータスを明示的に返します。 この場合、PlayFrameworkによって提供されるエラーページは表示されません。 。 代わりに、ブラウザは500 HTTPステータスコードを受け取り、そのエラーページを表示します。

http:// localhost:9000 / errors / notfoundページとhttp:// localhost:9000 / errors/badrequestページを開いたときにも同じことが起こります。 コントローラコードから明示的に返されたエラーステータスは、エラーハンドラによってインターセプトされません。

4.3. 実際のNotFoundページを取得する

もちろん、存在しないページアクセスすると(たとえば、http:// localhost:9000 / this_is_not_here)、デフォルトの NotFound エラーページが表示されます(ここでも、開発モードのみ):

このシナリオを処理し、要求されたページが存在しない場合に別のページにリダイレクトするために、カスタムエラーハンドラーを定義できます。

5. カスタムエラーハンドラ

カスタムエラーハンドラの作成は、新しいエラーハンドラクラスの実装から始まります。

class CustomErrorHandler extends HttpErrorHandler {
  def onClientError(request: RequestHeader, statusCode: Int, message: String): Future[Result] = {
    if (statusCode == NOT_FOUND) {
      // works only when we access a not existing page, not when we return NotFound on purpose
      Future.successful(Redirect(routes.ErrorDemoController.noError()))
    } else {
      Future.successful(
        Status(statusCode)("A client error occurred.")
      )
    }
  }

  def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = {
    Future.successful(
      InternalServerError("A server error occurred: " + exception.getMessage)
    )
  }
}

ページが存在しない場合は、ユーザーを / errors /noerrorページにリダイレクトすることに注意してください。 したがって、ErrorDemoControllerに新しいメソッドを追加する必要があります。

def noError(): Action[AnyContent] = Action {
  Ok
}

そして、routersファイルに新しいエントリを追加します。

GET /errors/noerror controllers.ErrorDemoController.noerror()

カスタムハンドラーを使用するには、application.confファイルで上書きする必要があります

play.http.errorHandler = "errors.CustomErrorHandler"

6. カスタムエラーハンドラのテスト

それでは、ブラウザでhttp:// localhost:9000 / errors/exceptionページを開きましょう。 エラーメッセージが表示された赤いページと強調表示されたアプリケーションコードが表示される代わりに、「サーバーエラーが発生しました」というメッセージが表示された白いページが表示されます。 これは、 onServerErrorメソッドがエラーを処理し、応答を生成したことを意味します。

デフォルトのエラーハンドラーと同様に、カスタムハンドラーは、アプリケーションコードから明示的にサーバーエラーを返す場合、サーバーエラーをインターセプトしません。 http:// localhost:9000 / errors / internalerrorページを開くと、ブラウザはステータス500を受け取り、エラーページを表示します。

クライアント側のエラーをテストするときにも同じ動作を観察できます。 http:// localhost:9000 / errors / notfoundを開くと、404 HTTPステータスの応答が生成され、ブラウザに「ページが見つかりません」というメッセージが表示されます。 ただし、http:// localhost:9000 / this_is_not_hereページを開くと、 onClientErrorメソッドのカスタムコードが実行され、がhttp:// localhost:9000 / errors/noerrorにリダイレクトされます。ページ。

6.1. 自動テストの作成

ルートコントローラーの単体テストを作成する場合、フレームワークはテスト中にカスタムエラーハンドラーを使用しません。 このように動作して、例外をインターセプトするエラーハンドラーではなく、コントローラーコードをテストできるようにします。

エラーハンドラ自体をテストするには、を使用する個別の単体テストを実装する必要があります onClientError また onServerError 直接メソッド:

class CustomErrorHandlerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting with Eventually {
  "CustomErrorHandler" should {
    "redirect when a page has not been found" in {
      //given
      val objectUnderTest = new CustomErrorHandler()
      val request = FakeRequest(GET, "/fake")
      val statusCode = StatusCodes.NotFound
      val message = ""

      //when
      val responseFuture = objectUnderTest.onClientError(request, statusCode.intValue, message)

      //then
      eventually {
        status(responseFuture) mustBe StatusCodes.SeeOther.intValue
      }
    }
  }
}

7. 結論

この記事では、Play Frameworkでのデフォルトのエラー処理をテストし、カスタムエラーハンドラーを使用してその動作を変更し、エラーステータスを明示的に返してもエラーハンドラーがトリガーされないことを示しました。

いつものように、ソースコードはGitHubにあります。