Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the output of RestSetResponse without making HTTP request

I have a minimal (example) REST end-point test/people.cfc:

component
  restpath = "test/people/"
  rest     = true
{
  remote void function create(
    required  string   first_name restargsource = "Form",
    required  string   last_name  restargsource = "Form"
  )
    httpmethod  = "POST"
    restpath    = ""
    produces    = "application/json"
  {
    // Simulate adding person to database.
    ArrayAppend(
      Application.people,
      { "first_name" = first_name, "last_name" = last_name }
    );

    // Simulate getting people from database.
    var people = Application.people;

    restSetResponse( {
      "status"  = 201,
      "content" = SerializeJSON( people )
    } );
  }
}

As noted here and in the ColdFusion documentation:

Note: ColdFusion ignores the function's return value and uses the response set using the RestSetResponse() function.

So the void return type for the function appears to be correct for the REST function.

Now, I know I can call it from a CFM page using:

httpService = new http(method = "POST", url = "https://localhost/rest/test/people"); 
httpService.addParam( name = "first_name", type = "formfield", value = "Alice" ); 
httpService.addParam( name = "last_name",  type = "formfield", value = "Adams" ); 
result = httpService.send().getPrefix();

However, I would like to call the function without making a HTTP request.

Firstly, the REST CFCs do not appear to be accessible from within the REST directory. This can be solved simply by creating a mapping in the ColdFusion admin panel to the root path of the REST service.

I can then do:

<cfscript>
Application.people = [];

people = new restmapping.test.People();

people.create( "Alice", "Adams" );

WriteDump( application.people );
</cfscript>

This calls the function directly and the output shows it has added the person. However, the response from the REST function has disappeared into the aether. Does anyone know if it is possible to retrieve the response's HTTP status code and content (as a minimum - preferably all the HTTP headers)?

Update - Integration Testing Scenario:

This is one use-case (of several) where calling the REST end-point via a HTTP request has knock-on effects that can be mitigated by invoking the end-point directly as a method of a component.

<cfscript>
// Create an instance of the REST end-point component without
// calling it via HTTP request.
endPoint = new restfiles.test.TestRESTEndPoint();

transaction {
  try {
    // Call a method on the end-point without making a HTTP request.
    endPoint.addValueToDatabase( 1, 'abcd' );
    assert( getRESTStatusCode(), 201 );
    assert( getRESTResponseText(), '{"id":1,"value":"abcd"}' );
    // Call another method on the end-point without making a HTTP request.
    endPoint.updateValueInDatabase( 1, 'dcba' );
    assert( getRESTStatusCode(), 200 );
    assert( getRESTResponseText(), '{"id":1,"value":"dcba"}' );
    // Call a third method on the end-point without making a HTTP request.
    endPoint.deleteValueInDatabase( 1 );
    assert( getRESTStatusCode(), 204 );
    assert( getRESTResponseText(), '' );
  }
  catch ( any e )
  {
    WriteDump( e );
  }
  finally
  {
    transaction action="rollback";
  }
}
</cfscript>

Calling each REST function via a HTTP request will commit the data to the database after each request - cleaning up between tests where the data has been committed can get very complicated and often results in needing to flashback the database to a previous state (resulting in integration tests being unable to be run in parallel with any other tests and periods of unavailability during flashbacks). Being able to call the REST end-points without making lots of atomic HTTP requests and instead bundle them into a single transaction which can be rolled back means the testing can be performed in a single user's session.

So, how can I get the HTTP status code and response text which have been set by RestSetResponse() when I create an instance of the REST component and invoke the function representing the REST path directly (without using a HTTP request)?

like image 446
MT0 Avatar asked Jan 24 '17 13:01

MT0


1 Answers

@MT0,

The solution will* involve a few steps:

  1. Change remote void function create to remote struct function create
  2. Add var result = {"status" = 201, "content" = SerializeJSON( people )}
  3. Change your restSetResponse(..) call to restSetResponse(result)
  4. Add return result;

* The solution will not currently work, b/c ColdFusion ticket CF-3546046 was not fixed completely. I've asked Adobe to re-open it and also filed CF-4198298 to get this issue fixed, just in case CF-3546046 isn't re-opened. Please see my most recent comment on CF-3546046, and feel free to vote for either ticket. Once either is fixed completely, then the above-listed changes to your code will allow it to set the correct HTTP response when called via REST and to return the function's return variable when invoked directly. Note: you could also specify a headers struct w/in the result struct in step 2, if you also want to return headers when the function is invoked directly.

Thanks!, -Aaron Neff

like image 130
Aaron Neff Avatar answered Nov 11 '22 07:11

Aaron Neff