Thursday, June 28, 2012

CF: ColdFusion 10 experimenting with alternate connector with IIS

If you know me, you know I really don't like the ISAPI connector Apache Tomcat has been using for ages to allow for connectivity between Apache Tomcat and IIS. This annoyed me sooo much I went to create an alternative with the BonCode connector.

When Adobe released ColdFusion 10, however, they built their connection mechanism on top of the original ISAPI connector for IIS. In addition, they made modifications to both the Tomcat server code and the ISAPI connector code to accommodate their particular needs.

The upside of this is things continue to mostly work like they have in the previous iterations, with maybe the only exception being, that you can no longer use CFFLUSH with the out of the box setup. You actually have to disable connector buffering (this has other side effects that you maybe OK with maybe not). To do so go to {CF-Home}/config/wscoonfig/{connector-no}/isapi_redirect.properties. Change iis_buffer_enable to false and restart the IIS.

By now you also know that Adobe does not recommend you use the default Tomcat instance to host anything else besides ColdFusion. A good blog post: "What’s the deal with Tomcat in ColdFusion 10?"  by Rupesh Kumar explains this in more detail.

On the other side, if you want to easily use one IIS to front-end multiple tomcat instances or applications, you really have to do something about the non-standard connector that ships with CF10. Your IIS gies otherwise completely monkey-crazy if you try to work with the Adobe supplied one.

Thus, time for a good experiment. I built in experimental support for the Adobe specific idiosyncrasies to the AJP protocol into the BonCode connector with version 1.0.2 and was curious whether I could get this to work with the release version of CF10.

The first step is actually to install CF10 as usual. Once the server install of CF10 is complete, however, you will need to remove the existing connector via the webserver config tool like so:


Once the removal of the Adobe connector is done, you can download the BonCode connector from RiaForge connector and start the standard install.

Accept all the defaults but once you get to the Tomcat information page, you will need to change the port to 8012 since this is the port CF10 will accept connections under for AJP, like so:



That's all folks. Now you can configure additional sites, and tomcat instances as outlined in the connector manual and your IIS server will happily serve you.

If you want to take this a little further  I would suggest a few tweaks:
a) If you want CFFLUSH support, check the appropriate option during the install of the connector on the options page.This is truly implemented as HTTP flush detection, rather than disabling of buffers and will not cause extra client or network overhead.
b) If you want the CGI.PATH_INFO variable to be populated exactly like CF10, you will need to change the connector setting file slightly. First, find out where the setting file is located by calling this URL on the server:
 http://localhost/a.cfm?BonCodeConnectorVersion=true
Then, add the PathInfoHeader directive like so to the setting file:
<PathInfoHeader>path-info</PathInfoHeader>

c) If you want to use the same packet size that Adobe defaults to, you can also change the packet size using the PacketSize directive in the setting file like so:

<PacketSize>65531</PacketSize>

This reduces the number of packets exchanged between IIS and CF Tomcat but uses a larger buffer block.

d) If you want to explore the full functionality of CF10 I would also recommend that you add the wildcard mappings to the appropriate sub-folders in your  CF10 site. This is used for some of its flex and background features:


/CFFormGateway
/flex2gateway
/CFFileServlet
/cfform-internal
/flashservices/gateway
/flex-internal
/rest


The directions on how to add wilcard mappings are also in the manual that is included in the download package for the BonCode connector.

Happy experimenting.

B.

UPDATE ! UPDATE ! UPDATE ! UPDATE! -- 8/1/2012

I added a setting specifically to address the behavior of the connector for Adobe backends with version 1.0.8 of the connector. This allows the connector to be changed with only one switch and everything should fall into place:


<EnableAdobeMode>True</EnableAdobeMode>


UPDATES: -- 5/30/2013

Adobe made changes that are only reflected in version 1.0.15 of Boncode. If you are using any earlier version you will need to change the server.xml file normally located here:

cf_root/runtime/conf/server.xml

Find line similar to:
<Connector port="8012" protocol="AJP/1.3" redirectPort="8443 packetSize="65531"/>

And add the packetSize directive. This is not needed if you use version 1.0.15 or higher of connector.

UPDATES -- 01/10/2014

More time spent going through the grunge work of analysis when people reported issues more changes discovered.  For example, the Adobe connector hard-codes but hides the packetSize attribute in tomcat. Adobe is actually using 65531 bytes as packet size now. Why? No one knows.
Version 1.0.18 (to be released shortly) of the connector makes this adjustment automatically when you turn on Adobe mode so you don't get odd pages with bytes missing. This is pretty mean. I made changes accordingly to above examples.
If you use standard Tomcat, but switch to Adobe mode, you will need to put this value into the Tomcat server.xml configuration file from now on.

UPDATES -- 02/17/2014

Some hard core testing has gone into release 1.0.18g of the connector and as a result of feedback from the community I have removed the "experimental" moniker for CF10. Will support you all the way now ;o)


UPDATES -- 10/18/2014

Please note that there are some changes to the default port used for Adobe ColdFusion 11. ColdFusion 11 uses port 8014 for AJP traffic.



As usual, feedback is appreciated. Please post on the Riaforge.org site.

Tuesday, June 26, 2012

CF: Installing ColdFusion 10 with IIS

I thought I write a quick note on the installation of CF10 on Windows, especially if you want to use IIS7.
Let's say you installed IIS with the most basic setup and the tried to install CF10.

During the setup of CF10 you will be prompted at the web server selection and configuration step to install more Role Services for IIS like this:



You would correctly interpret this to mean:
Install: ASP.NET, ISAPI Extensions and ISAPI Filter like so:



However, this is not quite accurate. Since you still will not be able to proceed.You will also need to add the CGI Role Service.

Best,
B.

Saturday, June 23, 2012

CF: breaking queries into pages of data

Another day another problem to solve. With many more Ajax type screens, managing the data flow to the front-end needs to be baked into the core system. Otherwise, it seems, the users really do not have a care in the world about how much data they consume....
In my specific case, I am receiving a query (structured data in records), any type of query from any type of DB. I do not know the number of columns, indexes, pks or anything. The job is to be able to return a page of records from this query without going back to the database (would be hard since we don't know the database either). This could even be a QofQ, i.e. code generated, set of records.
I scratched my head several times over this, but still do not think I have an elegant solution. Nonetheless, I have a working one.
I wanted to avoid looping over the query itself and creating a new one record by record. I decided to use a Query of Query approach though only when needed. To do that I dynamically attach another column to the original query that I am using as primary key. Then, I am calculating the start and end records for the page and using a QoQ to just return the ones requested for the Page.


<cffunction name="getQueryPage" access="public" returntype="query" hint="return a query with subset of records based on page argument.">

 <cfargument name="selRecords" required="Yes" type="query" hint="the base query with records">

 <cfargument name="Page" required="no" type="numeric" default="1" hint="which page (chunk) to return">

 <cfargument name="PageSize" type="numeric" default="50" hint="size of record chunk. this is also the max number of records to be returned.">



 <cfscript>

  //init stuff

  var dataQuery = arguments.selRecords; //realias

  var selReturn = dataQuery;

  var iPageCount = ceiling(dataQuery.recordCount/arguments.PageSize);

  var lstCols = dataQuery.ColumnList; // capture col list before change 

  var blnDoSubQuery = false;

  var intStartRec = 0;

  var intEndRec = arguments.PageSize;

  var zzArray = ArrayNew(1); 

   

  

  arguments.Page = int(Abs(arguments.Page)); //ensure that we have no negatives

  //if we request a page that is too big return last page

  if( arguments.Page GT iPageCount) arguments.Page = iPageCount;

  

  //attach control column to base data if calculations require it and or it is not present. 

  if (ListFindNoCase(lstCols,"zzzPageControlCol") IS 0 AND arguments.Page GT 0) {

   intStartRec = 1 + ((arguments.Page -1) * arguments.PageSize);

   intEndRec = intStartRec + (arguments.PageSize -1);   

   //only if the overall query has more records than requested do we need to do anything.

   if ( NOT (intStartRec IS 1 AND intEndRec GTE dataQuery.recordCount)) {

    blnDoSubQuery = true;

    //attach control col

    zzArray = ArrayNew(1);   

    //if we do not think that the passed in query is cached, we can probably use a scheme

    //were we only mark the records that we need to return.

    //however, marking all records one time is faster, if the base query is cached and repeatedly pages

    //need to be returned

    for (i=1; i LTE dataQuery.Recordcount; i++) {

     zzArray[i] = i;

    } 

    QueryAddColumn(dataQuery,"zzzPageControlCol","Integer",zzArray);      

   }; 

  }

 </cfscript>

 <!--- only do subquery if needed, ideally the base query is cached somehow but that is beyond this scope (bsoylu) --->

 <cfif blnDoSubQuery>

  <cfquery name="selReturn" dbtype="query" >

   SELECT #lstCols#

   FROM dataQuery

   WHERE zzzPageControlCol >= #intStartRec# AND zzzPageControlCol <= #intEndRec#

  </cfquery>

 </cfif>

 

 <cfreturn selReturn>

</cffunction>

Cheers,
B.