Showing posts with label ColdFusion. Show all posts
Showing posts with label ColdFusion. Show all posts

Friday, October 24, 2025

Hooked on Git Hooks

A useful feature of git is hooks.  There are two hooks that I find very helpful at my job.  The first is in my local development environment.  We have this simple post-checkout hook:

git rev-parse --abbrev-ref HEAD > www/app/includes/git-branch.cfm

What this does is write the name of the current branch to a ColdFusion file (yes, we still use ColdFusion) that is included on the footer of every page.  So when doing development or testing, we can just glance at the footer of the page to know which branch we are on.

The second is on our staging and production servers.  We have this post-merge hook:

rm -rf .vscode

rm -rf README.md

rm -rf www/app/testing

gitlog=$(git log -1 --pretty=format:"%s")

timestamp=$(date "+%Y/%m/%d %H:%M:%S")

echo "$timestamp  $gitlog" >> git-pulls.log

After we deploy our code using git, this removes unnecessary files and writes a simple log of what was deployed. 

Speaking of code deployments, we keep our process simple for our internal app.  To deploy changes, we simply do a git pull on the master branch.  If we need to back out changes, we have a short bash script for that:

#!/bin/bash

git reset --hard HEAD@{1}

I understand the need for CI/CD pipelines for bigger projects but sometimes the simple solutions are the best.

Wednesday, October 11, 2017

Rollback with CFTRANSACTION

You may be surprised to find out that software documentation is not always clear or complete. Shocking, I know. Even when it is, sometimes I just have to see things in action for myself. This is one of those cases.

A question came up today at work about the <cftransaction> tag. (Yes, we still use ColdFusion. There are dozens of us. Dozens!) The question was if the tag would automatically rollback changes or if you needed to explicitly type <cftransaction action="rollback"> to rollback the changes. I have always assumed the latter.

So here is my test case. First I created 2 test tables in the database for testing each with a test record.
CREATE TABLE test_table_1 (
  tt1_record_id NUMBER(3) NOT NULL,
  tt1_text VARCHAR2(100),
  CONSTRAINT pk_test_table_1 PRIMARY KEY (tt1_record_id)
);

CREATE TABLE test_table_2 (
  tt2_record_id NUMBER(3) NOT NULL,
  tt2_text VARCHAR2(100),
  CONSTRAINT pk_test_table_2 PRIMARY KEY (tt2_record_id)
);

INSERT INTO test_table_1 (
  tt1_record_id,
  tt1_text
)
VALUES (
  1,
  'Blue'
);

INSERT INTO test_table_2 (
  tt2_record_id,
  tt2_text
)
VALUES (
  1,
  'Triangle'
);
And now the ColdFusion code. This block of code first outputs the text from the database. Then it updates the first table while intentionally failing to update the second table. Lastly, it displays the text from the database.
<cfquery name="variables.qryBefore" datasource="#application.dsn#">
SELECT
  tt1_text, tt2_text
FROM
  test_table_1,
  test_table_2
WHERE
  tt1_record_id = tt2_record_id
</cfquery>

<p>
    <cfoutput>
        Before: 
        #variables.qryBefore.tt1_text#
        #variables.qryBefore.tt2_text#
    </cfoutput>
</p>

<cftry>
    <cftransaction>
        <cfquery name="variables.qryUpdate1" datasource="#application.dsn#">
            UPDATE
                test_table_1
            SET
                tt1_text = 'Red'
            WHERE
                tt1_record_id = 1
        </cfquery>

        <cfquery name="variables.qryUpdate2" datasource="#application.dsn#">
            UPDATE
                test_table_2
            SET
                tt2_text = 'Square'
            WHERE
                foo = bar
        </cfquery>
    </cftransaction>
    <cfcatch type="any">
        <p>
            Database update failed.  Changes should have been rolled back.
        </p>
    </cfcatch>
</cftry>

<cfquery name="variables.qryAfter" datasource="#application.dsn#">
SELECT
  tt1_text, tt2_text
FROM
  test_table_1,
  test_table_2
WHERE
  tt1_record_id = tt2_record_id
</cfquery>

<p>
    <cfoutput>
        After: 
        #variables.qryAfter.tt1_text#
        #variables.qryAfter.tt2_text#
    </cfoutput>
</p>
The database values remained the same. The first table was updated but when the second table failed to update, the changes were rolled back. I could have added <cftransaction action="rollback"> to the <cfcatch> block and gotten the same result.

So why use the rollback command then? You may only want to rollback to a certain savepoint. Another use case I have found for it is unit testing. In my test, I want to execute the entire method even the database code. However, before I leave the <cftransaction> block, I rollback my changes:
<cfif this.testing>
 <cftransaction action="rollback" />
</cfif>

Thursday, March 9, 2017

ColdFusion 2016 Broke My API (And How I Fixed It)

While on ColdFusion 9, I built a REST API for our an application at work. Everything was working fine until we upgraded to ColdFusion 2016. When I went to test the API, I got an HTTP 500 error. The Apache log gave me no clues to what was happening, so my next stop was the ColdFusion logs. In the exception log, I found this:
Error","ajp-nio-8015-exec-9","03/09/17","07:22:12",,"Application  could not be found. The specific sequence of files included or processed is: '''' "
javax.servlet.ServletException: Application  could not be found.
        at coldfusion.rest.servlet.CFRestServlet.invoke(CFRestServlet.java:512)
        at coldfusion.rest.servlet.RestFilter.invoke(RestFilter.java:60)
        at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:94)
        ...
A quick Google search did not reveal much. However, the fact that it was calling the CFResetServlet gave me an idea. What if the /api directory now had special meaning? So I renamed the /api directory to /API-TEST and sure enough my code was working again.
I found the solution in the web.xml file:
    <servlet-mapping id="coldfusion_mapping_16">
        <servlet-name>CFRestServlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
This mapping is forcing everything in the /api directory to be processed by the ColdFusion REST service. To fix this, I simply commented out this section of the configuration and restarted the ColdFusion service.
    <--
    <servlet-mapping id="coldfusion_mapping_16">
        <servlet-name>CFRestServlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
    -->

Thursday, October 6, 2016

Format JSON String in ColdFusion

I am sure this has probably already been done but I couldn't find it quickly with a Google search. So I wrote my own. This function takes a JSON string and indents it to make it more readable.
<cffunction name="indentJSON" hint="Indents JSON to make it more readable">
    <cfargument name="JSONString" default="" hint="JSON string to be formatted">
    <cfargument name="indentCharacters" default="#Chr(9)#" hint="Character(s) to use for indention">

    <cfset local.inQuotes = false>
    <cfset local.indent = 0>
    <cfset local.returnString = "">
    <cfset local.stringLength = Len(arguments.JSONString)>
    <cfloop index="i" from="1" to="#local.stringLength#">
        <cfset local.currChar = Mid(arguments.JSONString, i, 1)>
        <cfif i lt local.stringLength - 1>
            <cfset local.nextChar = Mid(arguments.JSONString, i + 1, 1)>
        <cfelse>
            <cfset local.nextChar = "">
        </cfif>
        <cfif local.currChar eq '"'>
            <cfset local.inQuotes = !local.inQuotes>
        </cfif>
        <cfif local.inQuotes>
            <cfset local.returnString = local.returnString & local.currChar>
        <cfelse>
            <cfswitch expression="#local.currChar#">
                <cfcase value="{">
                    <cfset local.indent = local.indent + 1>
                    <cfset local.returnString = local.returnString & "{" & Chr(10) & RepeatString(arguments.indentCharacters, local.indent)>
                </cfcase>
                <cfcase value="}">
                    <cfset local.indent = local.indent - 1>
                    <cfset local.returnString = local.returnString & Chr(10) & RepeatString(arguments.indentCharacters, local.indent) & "}">
                    <cfif local.nextChar neq ",">
                        <cfset local.returnString = local.returnString & Chr(10)>
                    </cfif>
                </cfcase>
                <cfcase value="," delimiters="Chr(0)">
                    <cfset local.returnString = local.returnString & "," & Chr(10) & RepeatString(arguments.indentCharacters, local.indent)>
                </cfcase>
                <cfcase value=":">
                    <cfif local.nextChar neq " ">
                        <cfset local.returnString = local.returnString & ": ">
                    </cfif>
                </cfcase>
                <cfdefaultcase>
                    <cfset local.returnString = local.returnString & local.currChar>
                </cfdefaultcase>
            </cfswitch>
        </cfif>
    </cfloop>

    <cfreturn trim(local.returnString)>
</cffunction>
And here's an example:
<cfset variables.testObject = {}>
<cfset variables.testObject.name.first = "Chad">
<cfset variables.testObject.name.last = "Armond">
<cfset variables.testObject.title = "Software Developer">

<cfset variables.testString = SerializeJSON(variables.testObject)>

<cfoutput>
    <h1>With Tabs (Default)</h1>
    <cfset variables.json1 = indentJSON(variables.testString)>
    <pre>#variables.json1#</pre>

    <h1>With Spaces</h1>
    <cfset variables.json2 = indentJSON(variables.testString, "    ")>
    <pre>#variables.json2#</pre>
</cfoutput>
And the results:

With Tabs (Default)

{
 "NAME": {
  "LAST": "Armond",
  "FIRST": "Chad"
 },
 "TITLE": "Software Developer"
}

With Spaces

{
    "NAME": {
        "LAST": "Armond",
        "FIRST": "Chad"
    },
    "TITLE": "Software Developer"
}

Wednesday, June 29, 2016

Including ColdFusion Content in Perl CGI Script

I have a site that mostly consists of ColdFusion pages. Occasionally I will use a Perl CGI script when the need arises. For example, long running reports or scripts that need to run shell commands are better suited for Perl than ColdFusion. On a side note, if you are running ColdFusion on a UNIX/Solaris platform like I am, you should avoid CFEXECUTE tags at all costs.

Whenever I create a Perl script on my site, I still want it to have the look and feel of the rest of my ColdFusion pages. I have a standard header that is included on every page of my site. Within that header is a menu that changes depending on which role(s) the user is assigned. To include the header on my Perl pages, I just use wget to retrieve the header file from my site, then display the HTML. It is something similar to this:
print &getHTMLHeader();

sub getHTMLHeader() {
    return `wget -O - http://mysite/myheader.cfm`;
}

The problem is that my ColdFusion session is not passed. Therefore, the menu does not display what a logged in user should see. I first thought I could just pass my cookies to the wget command:
sub getHTMLHeader() {
    open OUT, ">cookies.txt":
    print OUT $ENV{'HTTP_COOKIE'};
    close OUT;
    return `wget --load-cookies=cookies.txt -O - http://mysite/myheader.cfm`;
}

However, the ColdFusion session management is smart enough to recognize that something is not right about this session. That's because the IP address of the server and not the client is being passed to the page.

So here is the solution. After the HTML header is received, I append a little jQuery code to get the menu code and replace what is displayed:
sub getHTMLHeader() {
    my $html = `wget -O - http://mysite/myheader.cfm`;
    $html .= <<"    END";
        <script>
        \$.ajax({
            url: '/path/to/menu/menu.cfm',
            async: false,
            dataType: 'html',
            success: function (data, textStatus, jqXHR) {
                \$(".main-menu-content").html(data);
            }
        });
        </script>
    END
    return $html;
}

Thursday, August 27, 2015

Using jQuery and ColdFusion to Retrieve and Save a List of Files

The Issue: We are required to scan our Web sites at work to insure that they meet Section 508 guidelines for accessibility.

The Problem: The scanner that we are required to use does not recognize spider traps. Since the site I have to scan contains over 4 million records and thus over 4 million URLs, the scanner will take hours to complete a scan and then report the same issue 4 million times.

The Fix (First Iteration): When the site was smaller, I would just save an example of each page to a directory on the Web server, then scan that directory for issues. The problem with this approach was that 1) it required me to manually save these HTML files and 2) would require me to remember each file that needed to be checked.

The Fix (Second Iteration): I decided a better approach would be to create a list of all URLs that needed to be scanned. Then write a server-side script to retrieve each one of those pages using wget and save them to the server. But I ran into a problem. Authentication on this site is a single sign-on (SSO) application that works by redirecting the user to a log in page on a different server, then redirect back after the user has successfully logged in. Maybe that could be handled in the server side script, but I don't want to figure out the code to do that.

The Fix (Final Iteration): It then occurred to me that a better solution would be to retrieve these files through AJAX. I would require authentication for my main page and then the AJAX calls would include my credentials. Of course client-side JavaScript can't save files to the server, but I found a way around that. Here's an overview of the solution:

Diagram illustrating an overview of the process

My main ColdFusion page contains a list of relative URLs to test. Then it loops through those to generate AJAX requests. JavaScript then returns HTML data. I pass the file name and encoded HTML back to ColdFusion through a second AJAX call. Then that ColdFusion page saves the HTML to my specified data. Here's a snippet of the code:
<cfset variables.count = 0>
<cfloop array="#variables.pagesToCheck#" index="variables.url">
    <cfset variables.count = variables.count + 1>
    <script>
    $(function() {
        $("#current-file").text('Processing <cfoutput>#variables.url#</cfoutput>');
        $.ajax({
            url: '<cfoutput>#variables.url#</cfoutput>',
            async: false,
            dataType: 'html',
            error: function (jqXHR, textStatus, errorThrown) {
                $("#pbar").progressbar({value:<cfoutput>#variables.count#</cfoutput>});
                $('#file-list').append('<li style="font-weight: bold; color: red">Could not retrieve <cfoutput>#variables.url#</cfoutput>.  Error: ' + errorThrown + '</li>');
            },
            success: function (data, textStatus, jqXHR) {
                $.ajax({
                    async: false,
                    type: 'POST',
                    url: 'save_html.cfm',
                    dataType: 'json',
                    data: {
                        source: escape('<cfoutput>#variables.url#</cfoutput>'),
                        html: escape(data)
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        $("#pbar").progressbar({value:<cfoutput>#variables.count#</cfoutput>});
                        $('#file-list').append('<li>Could not process #variables.url#  Error: ' + errorThrown + '</li>');
                    },
                    success: function (data, textStatus, jqXHR) {
                        $("#pbar").progressbar({value:<cfoutput>#variables.count#</cfoutput>});
                        $('#file-list').append('<li>' + data.source + ' - ' + data.success + '</li>');
                    }
                });
            }
        });
    });
    </script>
</cfloop>
And then here is the source of the save_html.cfm page:
<cfset variables.file_name = ReReplace(form.source, "[^\w\_]", "-", "ALL")>
{
    "source": "<cfoutput>#form.source#</cfoutput>",
    "success":
<cftry>
    <cffile action="write" file="#application.webroot#/508/#variables.file_name#.html" output="#URLDecode(form.html)#">
        "Saved"
    <cfcatch type="any">
        "Failed to save"
    </cfcatch>
</cftry>
}
Now when I'm ready for 508 testing, I just run my page to create all of my HTML pages then set my scanner to my /508 directory.

Thursday, April 18, 2013

Catching and Logging JavaScript Errors

As browser-based applications become more JavaScript centric, it becomes harder to debug user issues. For years I have been logging and handling server side errors, but only recently have I started logging JavaScript errors. And it turns out the solution was very simple.

First, the JavaScript code. Near the top of my main JavaScript file that I include on every page of my application, I put this code that I found online and modified for my own use. I should note here that I am using jQuery (and you should to).
window.onerror = function(m,u,l){
    $.post(
        basePath + "/js_error.cfm", 
        {
            msg: m,
            url: u,
            line: l,
            window: window.location.href
        }
    );
    return true;
}
All this code is doing is catching untrapped errors and posting them to a page on the server. Now for the server-side code:



    


#errorReport#
The ColdFusion script write the error to a log file and then emails me the report.

For now this will blindly send me error reports in the background. But in the future I would like to pop up a form to the user to get more information such as asking what they were trying to do when they got the error. I built a prototype using the jQuery UI dialog module. However, I need to work out some more UX questions like:
- How often do I ask for input especially if it's an error they are getting on every page?
- Should I pilot this dialog to select users first?
- Would a live chat with tech support be an option (using web sockets)?

Friday, February 17, 2012

Only Writing One Set of Form Validation Code

Form validation is an important part of Web development. It provides the user feedback when they make a mistake or omit information. And it protects the integrity of the application data.

In the past I have written 2 sets of form validation: 1 client-side and 1 server-side. Server side form validation is essential. Client-side validation wasn't always necessary in the past, but provided a better user experience. However, as more and more dynamic client-side content is being generated, it is becoming more essential and users are not as tolerant of "press the back button to correct your errors".

So instead of writing 2 sets of validation code (one in JavaScript and one in ColdFusion), I now levy the power of AJAX to only write one set of form validation code.

The way it works is to submit the form data to the server first via AJAX. The server side code then generates any and all error messages based on the form data. If there are errors, it sends the error messages back to the browser as JSON array. If there are no errors, then it submits the form to the client.

So what if the user has JavaScript disabled (does anybody do that anymore?) or somehow JavaScript is bypassed? The same error messages are then presented back to the user regardless. Here's some sample code to make more sense of it.

First our HTML form:

Next we add the JavaScript to handle the AJAX check and form submission:

And finally the client-side code:
 
Blogger Templates