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);
}

Reply via email to