404エラーのWicket
内での
なかなか
目次
この記事の
調べた
各頁は
Web.xml を 使う
Servlet
の
Error Pages and Feedback Messages - Apache Wicket - Apache Software Foundationに
web.xml
タグ<error-page >
を追加します。 <filter-mapping> <filter-name>WicketFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> <error-page> <error-code>404</error-code> <location>/404</location> </error-page>
Application.java
web.xml
で追加した 404の URL に 対応する Page を mount する 実装を 追加します。 上記は、// 404 mountPage("/404", ErrorPage.class);
404エラーの ハンドリング方法 にも 書いた 内容ですが、 この 実装方法だと、 「 Wicket
に登録していない URL
に対しての ハンドリング」と なります。
アプリケーション内から明示的に 404
ページに飛ばすには、 下記のように AbortWithHttpErrorCodeException
をスローしたり、 下記のように、throw new AbortWithHttpErrorCodeException(404, "Festival is not Exist...");
RestartResponseException
に遷移したい ページを 指定する ことで、 明示的な ページ表示が できます。 個人的には、// ここではErrorPage.classが遷移したいページ throw new RestartResponseException(ErrorPage.class);
Java の Web フレームワークは 使っている 状況なので、 生 Servlet の 機能を 使用するのはとあまりお勧めできないかと 考えます。
ApplicationSettings#set…Page() を 使う
Web.xml を
Application#set...Page()
とPage()
で
以下末尾がPage()
の
ApplicationSettings#setPageExpiredErrorPage()
PageExpiredException
に
ApplicationSettings#setInternalErrorPage()
予期しない
ApplicationSettings#setAccessDeniedPage()
AuthorizationException
、ListenerInvocationNotAllowedException
が
補足
AuthenticatedWebApplication
を継承した Application
クラスを作成している 場合、 getSignInPageClass
でLoginページを 指定する 必要が あります。 ページアクセス時に
認証が 通っていない 場合、 getSignInPageClass
での指定ページ、 または Pageに 制御が うつってからの 権限エラーと いう 状況の 場合では、
AuthorizationException
が発生する ことを 許容します。 ApplicationSettings
で指定した 設定は、 DefaultExceptionMapper#mapExpectedExceptions()
と、DefaultExceptionMapper#mapUnexpectedExceptions
で、使用されています。
以下、DefaultExceptionMapper
の実装抜粋に なります。
DefaultExceptionMapper.java
/** * Maps expected exceptions (i.e. those internally used by Wicket) to their corresponding * {@link IRequestHandler}. * * @param e * the current exception * @param application * the current application object * @return the {@link IRequestHandler} for the current exception */ protected IRequestHandler mapExpectedExceptions(Exception e, final Application application) { if (e instanceof StalePageException) { // If the page was stale, just re-render it // (the url should always be updated by an redirect in that case) return new RenderPageRequestHandler(new PageProvider(((StalePageException)e).getPage())); } else if (e instanceof PageExpiredException) { return createPageRequestHandler(new PageProvider(Application.get() .getApplicationSettings() .getPageExpiredErrorPage())); } else if (e instanceof AuthorizationException || e instanceof ListenerInvocationNotAllowedException) { return createPageRequestHandler(new PageProvider(Application.get() .getApplicationSettings() .getAccessDeniedPage())); } else if (e instanceof ResponseIOException) { logger.error("Connection lost, give up responding.", e); return new EmptyRequestHandler(); } else if (e instanceof PackageResource.PackageResourceBlockedException && application.usesDeploymentConfig()) { logger.debug(e.getMessage(), e); return new ErrorCodeRequestHandler(404); } return null; } /** * Maps unexpected exceptions to their corresponding {@link IRequestHandler}. * * @param e * the current exception * @param application * the current application object * @return the {@link IRequestHandler} for the current exception */ protected IRequestHandler mapUnexpectedExceptions(Exception e, final Application application) { final ExceptionSettings.UnexpectedExceptionDisplay unexpectedExceptionDisplay = application.getExceptionSettings() .getUnexpectedExceptionDisplay(); logger.error("Unexpected error occurred", e); if (ExceptionSettings.SHOW_EXCEPTION_PAGE.equals(unexpectedExceptionDisplay)) { Page currentPage = extractCurrentPage(); return createPageRequestHandler(new PageProvider(new ExceptionErrorPage(e, currentPage))); } else if (ExceptionSettings.SHOW_INTERNAL_ERROR_PAGE.equals(unexpectedExceptionDisplay)) { return createPageRequestHandler(new PageProvider( application.getApplicationSettings().getInternalErrorPage())); } // IExceptionSettings.SHOW_NO_EXCEPTION_PAGE return new ErrorCodeRequestHandler(500); }
DefaultExceptionMapper
では、ExceptionSettings
に
ExceptionSettings#set…() を 使う
Application
クラスのinit
メソッドでException
発生時の、
getExceptionSettings().setAjaxErrorHandlingStrategy(ExceptionSettings.AjaxErrorStrategy.REDIRECT_TO_ERROR_PAGE); getExceptionSettings().setThreadDumpStrategy(ExceptionSettings.ThreadDumpStrategy.THREAD_HOLDING_LOCK); getExceptionSettings().setUnexpectedExceptionDisplay(ExceptionSettings.SHOW_NO_EXCEPTION_PAGE);
ExceptionSettings#setAjaxErrorHandlingStrategy()
Ajax
リクエストでError
に
設定値に
AjaxErrorStrategy.REDIRECT_TO_ERROR_PAGE
通常のリクエストと 同様に ApplicationSettings#setInternalErrorPage()
で設定した
エラーページに遷移させます。 AjaxErrorStrategy.INVOKE_FAILURE_HANDLER
Javascript
側のcallback
メソッドに制御を 任せて、
AjaxCallListener#onFailure
メソッドを呼びだします。
ExceptionSettings#setThreadDumpStrategy()
スレッドダンプの
ThreadDumpStrategy.NO_THREADS
出力しない。ThreadDumpStrategy.THREAD_HOLDING_LOCK
Lock
を取得している Thread
のStackTrace
を出力する ThreadDumpStrategy.ALL_THREADS
Application
の全ての Thread
のStackTrace
を出力する
ExceptionSettings#setUnexpectedExceptionDisplay()
予期しない
ExceptionSettings.SHOW_EXCEPTION_PAGE
ExceptionErrorPage
に遷移します。 ExceptionSettings.SHOW_INTERNAL_ERROR_PAGE
ApplicationSettings#setInternalErrorPage()
で指定した ページに 遷移します。 ExceptionSettings.SHOW_NO_EXCEPTION_PAGE
500エラーを返して、 Wicket
ではなく、Web.xml
側で指定した
エラーページに遷移します。
IExceptionMapper を 使う
DefaultExceptionMapper
の
以下、Github
でexample
を
上記をExceptionMapper
の
継承クラスだけでなく、Application#getExceptionMapperProvider()
をExceptionMapper
のExceptionMapperProvider
を
なかなかわかりにくいですが、
- WicketApplication.java の
対象実装の 抜粋 /** * the exceptionMapper */ private final AppExceptionMapper exceptionMapper = new AppExceptionMapper(); /** * IProvider exceptionMapperProvider */ private final IProvider<IExceptionMapper> exceptionMapperProvider; public WicketApplication() { this.exceptionMapperProvider = () -> exceptionMapper; } /* (non-Javadoc) * @see org.apache.wicket.Application#getExceptionMapperProvider() */ @Override public IProvider<IExceptionMapper> getExceptionMapperProvider() { return exceptionMapperProvider; }
IRequestCycleListener#onException() を 使う
以下に
* How to use a different Wicket expiration/error page for popups and other special pages? - Stack Overflow
Application
クラス内で、IRequestCycleListener
を
エラーハンドリング処理を
エラーハンドリングが
IExceptionMapper
とIRequestCycleListener#onException()
の 挙動の 違いに ついて
以下の
少なくとも、
WicketApplication.java
@Log4j2 public class WicketApplication extends WebApplication { /** * the exceptionMapper */ private final AppExceptionMapper exceptionMapper = new AppExceptionMapper(); /** * IProvider exceptionMapperProvider */ private final IProvider<IExceptionMapper> exceptionMapperProvider; public WicketApplication() { this.exceptionMapperProvider = () -> exceptionMapper; } /** * @see org.apache.wicket.Application#init() */ @Override public void init() { super.init(); // Add RequestCycleListener getRequestCycleListeners().add(new AbstractRequestCycleListener() { @Override public IRequestHandler onException(RequestCycle cycle, Exception ex) { // Log log.info("============================================================"); log.info("Raise Exception", ex); log.info("============================================================"); return super.onException(cycle, ex); } }); // Do Somethings.. } /* (non-Javadoc) * @see org.apache.wicket.Application#getExceptionMapperProvider() */ @Override public IProvider<IExceptionMapper> getExceptionMapperProvider() { return exceptionMapperProvider; }
AppExceptionMapper.java
import lombok.extern.log4j.Log4j2; import org.apache.wicket.DefaultExceptionMapper; import org.apache.wicket.request.IRequestHandler; /** * AppExceptionMapper * * @author Kem */ @Log4j2 public class AppExceptionMapper extends DefaultExceptionMapper { @Override public IRequestHandler map(Exception e) { log.info("---------------------------------------"); log.info("Raise Exception>>>", e); log.info("--------------------"); return super.map(e); }
ExceptionMapper
と、RequestCycleListener#onException
の双方を 実装して、 どちらが 優先で ハンドリングされるのかを 確認してみました。
エラー発生時にログは 以下の 通り 出力されました。 20:10:11.728 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.WicketApplication - ============================================================ 20:10:11.729 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.WicketApplication - Raise Exception java.lang.NullPointerException 20:10:11.730 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.WicketApplication - ============================================================ 20:10:11.730 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.AppExceptionMapper - --------------------------------------- 20:10:11.730 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.AppExceptionMapper - Raise Exception>>> java.lang.NullPointerException 20:10:11.731 [dw-51] INFO xyz.monotalk.festivals4partypeople.web.AppExceptionMapper - --------------------
ハンドリングの 優先順位に ついて
ハンドリングの
1. IRequestCycleListener#onException()
2. IExceptionMapper#map()
と
どちらを 使用すべきか
IRequestCycleListener#onException()
はRequestCycle
を
それにIExceptionMapper#map()
はException
のみが
IRequestCycleListener#onException()
の
個人的には、IExceptionMapper#map()
。
遷移元ページ等、ReuqestCycle
のIRequestCycleListener#onException()
に
まとめ
以下まとめます。
Wicket は、 例外ハンドリングの ための 拡張ポイントが 多数実装されている。
拡張ポイントが 多数実装されているので、 正直使い方は 迷う(学習コストが 高い)。
考えた 限り 以下のような 判断で 使うのが よさそう。
Web.xml
では、Wicket
フレームワーク外のエラーページを 指定する。 これは セーフティーネットと しての 設定を 行う スタンス。 ApplicationSettings
、ExceptionSettings
は主に 開発用で、 これらを 使って 開発環境での エラー調査しやすくする。 IExceptionMapper#map()
でアプリケーション全体の カスタムエラーハンドリングする。
これは、認証エラー的な ものを ハンドリングする イメージです。 IRequestCycleListener#onException()
ではReuqestCycle
の情報が 取得できる。 画面の 状態に 依る 共通の ハンドリング処理を 実装する。
大規模な
一箇所に
以上です。
コメント