0

Dear domino cognoscenti,

I've just written a simple xAgent using an xpage which works for GET requests but when I submit a POST request I get an Error 500 HTTP Web Server: Command Not Handled Exception with the following detail CLFAD0384E: Page instance not found. The $$viewid ID was not present in a POST request (thanks Per for the hint).

Here's the code. What am I doing wrong? Do xpages not handle POST requests by default? Do I have to enable this or something?

Thanks very much for your time!

<?xml version="1.0" encoding="UTF-8"?>

<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
    <xp:this.resources>
    </xp:this.resources>

    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript:      
            var exCon = facesContext.getExternalContext(); 
            var requestMethod = exCon.getRequest().getMethod();
            var response = exCon.getResponse();
            var writer = facesContext.getResponseWriter();

            response.setContentType("text/plain");
            response.setHeader("Cache-Control", "no-cache");
            
            writer.write(requestMethod);
            writer.endDocument();
        }]]>
    </xp:this.afterRenderResponse>
</xp:view>

Edited 16 October 2022 I've tried the following:

  • Added viewState="nostate" to xp:view. (thanks Mark for that)
  • Had no code in the afterRenderResponse other than logging - still get the same problem.
  • Used code in the beforeRenderResponse - same problem.
  • Tried the restService - same.

The only thing that appears to fix it is:

  • Introduce a field called $$viewid and set this to the value '!!' or '!0!' or similar.
  • Use the FormData Web API on the browser to build the request body.
  • Send it as Content-Type:multipart/form-data.

Sending it as plain text, application/json etc doesn't work. For some reason the xpage won't accept other encoding types.

Any ideas anyone?

Adam
  • 5,495
  • 2
  • 7
  • 24
  • Check the stack trace in the XPages log file for details on the error – Per Henrik Lausten Oct 14 '22 at 17:52
  • I get "CLFAD0384E: Page instance not found. The $$viewid ID was not present in a POST request". From here https://stackoverflow.com/questions/14380359/significance-of-hidden-fields-viewid-xspsubmitid-xspexecid-xspsubmitv it appears that this is associated with the backend controls (although there are none on my xpage). How do I get to know what value I need? Many thanks in advance. – Adam Oct 14 '22 at 18:28
  • Just to add, I've tried adding a field to the form performing the POST request with the name $$viewid and tried several values and I'm getting the error 'String Transform operation not supported'. If there's any walkthrough to handling POST request via xAgents you can point me to instead, that'd be much appreciated. – Adam Oct 14 '22 at 18:30
  • Just an update, I found something here https://stackoverflow.com/questions/27396385/xpage-edit-read-mode-error-the-form-does-not-contain-an-input-viewid regarding the use of the
    tag so I also did a POST request using the fetch API in the browser and I'm getting the same result
    – Adam Oct 14 '22 at 21:28
  • Take a look at Jakarta EE for XPages for building a proper API: https://github.com/OpenNTF/org.openntf.xsp.jakartaee – Per Henrik Lausten Oct 15 '22 at 11:24

3 Answers3

3

I haven't tested it, but it might help to add viewState="nostate" to the xp:view. That's a good idea for XAgents anyway. See here for an explanation.

Mark Leusink
  • 3,747
  • 15
  • 23
2

The issue is with CSRF protection as detailed in this blog post. When creating an xpage, in xsp.properties, the setting xsp.csrf.protection=true needs to be set to false (annoyingly it's not part of the UI, you have to go to the source tab).

The explainer from the blog post is as follows.

If you look in a new NSF the xsp.properties contains a setting xsp.csrf.protection=true .

This enables a built-in framework for CSRF protection and was very easily retrofitted to our existing NSFs.

When this feature is enabled every XPage contains a token such as

<input type=”hidden” name=”$$viewid” id=”view:_id1__VUID” 
value=”!1to6ah3iyy1uc2gquai14d7ik!”>

If the returned XPage does not have this same token then an error is generated. The error will be CLFAD0253E: Cannot find a page instance corresponding to the ID: $$viewid=!aaaaaaaaaa!. It may have expired. Try loading the web page

Setting this to false prevents this from occuring and the code works as expected (however see here for the multipart/form-data) Obviously there are risks so this isn't ideal but it's the cause of the problem seen. Setting this from true to false stops this from occurring.

Thanks to Sean Cull at Focul for pointing this out. If following this path, we'd have to code our own CSRF token handler.

Thanks to everyone else who contributed above. I appreciate your time.

Adam
  • 5,495
  • 2
  • 7
  • 24
1

I do this a lot - though using Java beans. Let me show you:

<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false" viewState="nostate">
    <xp:this.beforeRenderResponse><![CDATA[#{javascript:ServiceController.process()}]]></xp:this.beforeRenderResponse>
</xp:view>

Not sure if this is an important difference but I use the "beforeRenderResponse" event...

In my Controller bean I have this public method:

public void process() {
    String method = null;
    try {
        setLanguage(null);
        method = Util.getHttpRequest().getMethod();
        if ("POST".equalsIgnoreCase(method)) {
            processPostRequest();
        } else {
            processGetRequest();
        }
    } catch (Exception e) {
        // Make sure any uncaught errors are logged and a proper response is returned
        sendResponse(new Error(e.getLocalizedMessage()));
    }
}

The "Util" class has these helpers:

public static ExternalContext getExternalContext() {
    return getFacesContext().getExternalContext();
}
public static XSPContext getXspContext() {
    return XSPContext.getXSPContext(getFacesContext());
}
public static HttpServletRequest getHttpRequest() {
    return (HttpServletRequest) getExternalContext().getRequest();
}

In the processPostRequest() method I get the data from the request through the request reader. In my sample I use Json to parse the contents to JSON like this:

List<PostObject> postList = new ArrayList<PostObject>();
JsonJavaFactory factory = JsonJavaFactory.instanceEx;
JsonJavaObject jsonObjs = (JsonJavaObject) JsonParser.fromJson(factory, Util.getHttpRequest().getReader());

As you send plain text you would just read the data from the reader.

HTH - happy coding ;-)

/John

John Dalsgaard
  • 2,797
  • 1
  • 14
  • 26