Hi, Some introduction: My users started complaining about error message "Forms require that the request method be POST and that the t:formdata query parameter have values.". I understand why it was raised: Tapestry 5's Form component always used to complain when submit was invoked as GET operations (e.g. somebody typed "http://host/mypage.myform" URL directly in the address bar) . Pre-5.4 it was not big issue, because it was rather unusual to users to accidentally invoke submit URL as GET operations, as Tapestry always used Redirect-After-POST approach, and submit URLs typically were not saved by browsers in the history. However Tapestry 5.4 does not redirect after POST in case of server-side validation errors on submitted forms (e.g. password checking). IIRC, this change was introduced to avoid unnecessary creation of server side sessions.
Again, I understand that. But now submit URLs are often exposed address bar (e.g. every time a user makes mistake entering password), stored in the browser's history, and - most importantly - suggested by browsers when users start typing the application address. I believe such behaviour leads to bad usability and leaves unpleasant impression of Tapestry's application. As far as I can tell there is no simple work around to avoid the exception (beside totally rewriting Form component). Only way I found that works reasonably well is to suppress the exception in RequestExceptionHandler and perform some other action - in my case: redirect to the page - see code below. However detecting this particular exception is tricky as it is just generic RuntimeException (why?) and the message content is localised (see org.apache.tapestry5.corelib.components.Form#executeStoredActions - around line 700 https://github.com/apache/tapestry-5/blob/master/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java#L701), so detecting is quite fragile. My questions: - do you know any better way to deal with this problem? - do you see any potential issues with my solution? And appeal to Tapestry's creators: please introduce a more specific exception class and/or allow application developers to supply their own strategy dealing with such situation. Best regards, Cezary // skipped header (imports etc.)... public class FormPostProblemWorkaroundExceptionHandler implements RequestExceptionHandler { private static final Logger LOGGER = LoggerFactory.getLogger (FormPostProblemWorkaroundExceptionHandler.class); private final RequestExceptionHandler delegate; private final RequestGlobals requestGlobals; private final BaseURLSource baseURLSource; private final PageRenderLinkSource pageRenderLinkSource; public FormPostProblemWorkaroundExceptionHandler( final RequestExceptionHandler delegate, final RequestGlobals requestGlobals, final BaseURLSource baseURLSource, final PageRenderLinkSource pageRenderLinkSource ) { this.delegate = delegate; this.requestGlobals = requestGlobals; this.baseURLSource = baseURLSource; this.pageRenderLinkSource = pageRenderLinkSource; } @Override public void handleRequestException(final Throwable exception) throws IOException { if (isExceptionAboutFormExpectingPostMethod(exception)) { redirect(exception); return; } delegate.handleRequestException(exception); } private boolean isExceptionAboutFormExpectingPostMethod(final Throwable exception) { // unfortunately, original exception is just RuntimeException and its message is localised // (see Tapestry message ID core-invalid-form-request), only constant information is reference to t:formdata argument // see org.apache.tapestry5.corelib.components.Form.executeStoredActions() return exception instanceof OperationException && exception.getCause() instanceof ComponentEventException && exception.getMessage().contains(Form.FORM_DATA); } private void redirect(final Throwable exception) throws IOException { final Request request = requestGlobals.getRequest(); final String failurePage = (request.getAttribute(InternalConstants. ACTIVE_PAGE_LOADED) == null) ? null : requestGlobals.getActivePageName(); final Response response = requestGlobals.getResponse(); if (failurePage == null) { final String rootURL = baseURLSource.getBaseURL(request.isSecure()); LOGGER.info("Redirecting to root URL {} after '{}'", rootURL, exception.getMessage()); response.sendRedirect(rootURL); return; } final Link link = pageRenderLinkSource.createPageRenderLink(failurePage); LOGGER.info("Redirecting to page {} after '{}'", link, exception.getMessage()); response.sendRedirect(link); } } // add this method in your AppModule class @Decorate(serviceInterface = RequestExceptionHandler.class) public static RequestExceptionHandler installWorkaroundForFormPOSTProblem( @Nonnull final RequestExceptionHandler delegate, @Nonnull final RequestGlobals requestGlobals, @Nonnull final BaseURLSource baseURLSource, @Nonnull final PageRenderLinkSource pageRenderLinkSource ) { return new FormPostProblemWorkaroundExceptionHandler(delegate, requestGlobals, baseURLSource, pageRenderLinkSource); }