myfaces-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Giampaolo Tomassoni" <Giampa...@Tomassoni.biz>
Subject RE: GET on page with <h:form> handled like form submission
Date Wed, 17 Jan 2007 12:47:13 GMT
From: Simon Kitching [mailto:simon.kitching@rhe.co.nz]
> 
> Giampaolo Tomassoni wrote:
> > Dears,
> 
> Ciao Bella ;)

Yuhuuu :)


> > I'm facing a problem with myfaces-1.4 in a framed web app under 
> JBoss Seam (http://labs.jboss.com/portal/jbossseam) and it seems 
> that the problem needs upstream support (you).
> > 
> > The problem is the following. When a page with parameters and 
> an <h:form> in it is first displayed through a GET, myfaces 
> correctly issues the phase events RESTORE_VIEW, then 
> RENDER_RESPONSE. However, when a subsequent GET attempts showing 
> the very same page, possibly with different parameters, myfaces 
> invokes the following phases instead: RESTORE_VIEW, 
> APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, 
> INVOKE_APPLICATION and RENDER_RESPONSE. This basicly means that 
> myfaces handles the subsequent GET as a form submission, which 
> may not always be the case.
> > 
> > In my case, in example, the GET parameters are used to specify 
> an item in a table. The <h:form> is then used to edit some of the 
> fields in that item. When the <h:form> is submitted through a 
> POST, the GET parameters which are used to identify the edited 
> item are not supplied in the form content: they are instead 
> automatically otained by a per-conversation context handled by JBoss Seam.
> > 
> > So, handling a GET with parameters as a form submission drives 
> Seam to simply ignore the parameters and apply the values stored 
> in the conversation context. The net effect is that once a page 
> with parameters is displayed, it is not anymore possible to 
> "switch" to the same page with different parameters.
> > 
> 
> GET isn't handled any differently from POST in MyFaces, and as far as I 
> know that's the correct behaviour. GET and POST are just two different 
> ways of encoding the parameters. In plain html, a form's method can be 
> set to "GET" or "POST".
>    <form method="get" action="/my/postback/url">
>      <input name="data" size="10">
>    </form>
> So I don't believe it's a myfaces bug.

I did suspect something like this.


> What you are seeing in terms of JSF phases is expected; if view X is 
> requested by the browser and there is a component tree to restore for 
> that view, then the phases APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, 
> UPDATE_MODEL_VALUES, INVOKE_APPLICATION happen. If there is no existing 
> component tree for that view, then RESTORE_VIEW does not succeed so 
> processing skips straight to RENDER_RESPONSE.
> 
> When using client-side state saving, the behaviour you want happens 
> automatically, because the POST will include a hidden field that 
> contains a serialized component tree (hence RESTORE_VIEW can be 
> performed). The GET command will not contain that hidden field, so 
> RESTORE_VIEW will not be possible and myfaces will move directly to 
> RENDER_RESPONSE.
> 
> However when using server-side state saving, the component tree can be 
> found regardless of whether GET or POST is used, so RESTORE_VIEW succeeds.

Well, right. But even in server-side state saving I see an <h:form> is encoded in a
<form> with some <hidden> fields. So, I guess that a GET would need to supply
the values of these <hidden>s in order to be identified as a postback.

This may mean that there are ways to better identify a GET as a postback, but actually myfaces
doesn't seem to pay too much attention to them.


> As it happens, in the application I'm working on we have what seems to 
> be a similar problem to you (though we don't use Seam); we are using 
> server-side state saving and want "GET" requests to show a fresh view of 
> the page even when the GET refers to the same view we recently rendered.

Right. Exactly my case.


>  > Please note that the same doesn't hold when a page with <h:form> is
>  > invoked by a GET without parameters: it always gets a RESTORE_VIEW
>  > followed by a RENDER_RESPONSE cycle.
> 
> I'm surprised by that; are you quite sure? Either the component tree can 
> be found or it can't, and a few random parameters aren't going to change 
> that...

I confirm this behaviour. Maybe this is due to some kind of optimization in myfaces: when
a get doesn't supply parameters, then it possibly can't be any (usefull) form submission.

As written above, this may not cause problems to empty <h:form>s: they anyway get <hidden>s
in them which must probably cause some kind of parameter values to be carried by a postback
GET.


>  > Is there any way to circumvent this problem? Is the way myfaces
>  > handles GET with parameters a by-design behaviour? Is so, which is the
>  > purpouse? Is there a way (maybe by mean of some init param) to
>  > instruct facelets to handle GETs always with a RESTORE_VIEW and
>  > RENDER_RESPONSE cycle, and not as a form submission?
> 
> 
> Our solution is a custom PhaseListener that checks the method property 
> of the request, and discards any component tree that may have been 
> retrieved during RESTORE_VIEW (thus forcing a jump to RENDER_RESPONSE).
> 
>      public void afterPhase(PhaseEvent event) {
>          if (event.getPhaseId().equals(PhaseId.RESTORE_VIEW)) {
>              // Never do a postback on GET request.
>              // Ideally this check would be done in
>              // before-restore-view, but JSF provides no way for
>              // code to skip the restore-view processing. We
>              // therefore need to let the normal restore-view
>              // take its course, then forcibly override the results
>              // here :-(.
>              FacesContext fc = event.getFacesContext();
>              ExternalContext ec = fc.getExternalContext();
>              HttpServletRequest hreq = (HttpServletRequest)
>                 ec.getRequest();
>              if (hreq.getMethod().equals("GET")) {
>                  // discard UIViewRoot created during restoreView
>                  // and use a new one
>                  UIViewRoot viewRoot = fc.getViewRoot();
>                  String viewId = viewRoot.getViewId();
>                  UIViewRoot newView =
>                   fc.getApplication().
>                    getViewHandler().createView(fc, viewId);
>                  newView.setViewId(viewId);
>                  fc.setViewRoot(newView);
>                  fc.renderResponse();
>              }
>          }
>      }
> 
> Note that this code checks PhaseId==RESTORE_VIEW because our version of 
> this listener actually does several things, so is active for all phases. 
> If your version only returns RESTORE_VIEW as its active phase then this 
> check is not needed.

Thank you very much for sharing your code with me: I didn't even know where starting "patch"
jsf to fix my problem.

I guess that jsf-1.2 attempts fixing the "is it a postback?" problem, since it defines a isPostback()
getter in the ResponseStateManager class. I suspect this is done exactly to allow the renderkit
to more precisely discriminate "show-the-page" requests from true postback ones.

Even not implementing the full jsf-1.2 specs, I guess that myfaces should apply more effort
in discriminating the two cases. Please note also that method org.apache.myfaces.shared_impl.renderkit.html.HtmlFormRendererBase.encodeBegin
in myfaces-1.1.4 says:

<snip>
        writer.startElement(HTML.FORM_ELEM, htmlForm);
        writer.writeAttribute(HTML.ID_ATTR, clientId, null);
        writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
        writer.writeAttribute(HTML.METHOD_ATTR, "post", null);
        writer.writeURIAttribute(HTML.ACTION_ATTR,
                                 facesContext.getExternalContext().encodeActionURL(actionURL),
                                 null);
</snip>

Which means that every and each <h:form> in myfaces-1.1.4 is to be handled through a
POST request. This may mean that every and each GET request may be seen as not being a postback,
but it seems that myfaces-1.1.4 does not enforce this. Please see the following excerpt from
the org.apache.myfaces.lifecycle.LifecycleImpl.execute method:

<snip>
	if (facesContext.getExternalContext().getRequestParameterMap().isEmpty())
	{
		//no POST or query parameters --> set render response flag
		facesContext.renderResponse();
	}
</snip>

See? A GET is a postback if and only if it carries parameters (which is compatible with my
experience about refreshing pages with no parameters). Even wider, a postback is as such irregardless
of the carring method (POST or GET), which is compatible with what you told me about a JSF
postback being carried by a GET, but which also seems not compatible with the specific myfaces-1.1.4
impl. of an html form.

So, would it be possible to assert that, in the myfaces implementation, postbacks are handled
only by POST (maybe non-empty) requests? What is supposed to be the payload of a postback
GET when all the forms use POST methods instead? I don't have such a deep knowledge of the
JSF framework to reply to this.


> Cheers,
> 
> Simon

Thanks Simon. I really appreciate your help.

Cheers dear, :)

Giampaolo


Mime
View raw message