Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with:

Normalizing HTTP Header Values Using CFHttp And ColdFusion

By Ben Nadel on
Tags: ColdFusion

Last week, Saravana Muthu point out that my Sticky CDN project (a content delivery network for local development environments) couldn't handle complex values in HTTP request header responses. To be honest, in all the years that I've been rocking out in ColdFusion, I never noticed that header values could be anything but simple values. But, as it turns out, ColdFusion will group like-named headers into a complex object structure.

According to the ColdFusion documentation, header values with the same name will be grouped into an array:

The response headers formatted into a structure. Each element key is the header name, such as Content-Type or Status_Code. If there is more than one instance of a header type, the type values are put in an array.

It turns out that this isn't actually true; ColdFusion doesn't put them into an Array - it puts them into a Struct that has index-based keys. So, you can't call arrayToList() or arrayLen() on it; but, you can iterate over the keys and then access the sub-values like they are array indicies.

To explore this behavior, I set up a page that sets two CFHeader values using the same name:

  • <!--- Notice that we are providing two header entries, both with the name "X-Test." --->
  • <cfheader name="X-Test" value="first-name=Kim" />
  • <cfheader name="X-Test" value="last-name=Smith" />
  •  
  • <cfcontent
  • type="text/plain"
  • variable="#charsetDecode( 'true', 'utf-8' )#"
  • />

As you can see, both CFHeader tags attempt to set an HTTP header with the name "X-Test".

Then, I created a demo page that makes a CFHttp request to consume the above page and examine the headers. In the following code, I'm dumping out the raw response as well as a normalized header response:

  • <cfscript>
  •  
  • // Get our remote resource that will send back multiple HTTP Headers with the same
  • // name, but different values.
  • resource = (
  • "http://" &
  • cgi.server_name &
  • getDirectoryFromPath( cgi.script_name ) &
  • "get.cfm"
  • );
  •  
  • resourceRequest = new Http(
  • method = "get",
  • url = resource,
  • getAsBinary = "yes"
  • );
  •  
  • result = resourceRequest.send().getPrefix();
  •  
  • // Output the raw response.
  • writeDump(
  • var = result,
  • label = "Multi-Header Test"
  • );
  •  
  • // Output the normalized headers in which each header value is a simple value.
  • writeDump(
  • var = getNormalizedHeaders( result.responseHeader ),
  • label = "Normalized Headers"
  • );
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • /**
  • * I return a normalized set of headers in which every value is a "simple value".
  • * if tere are headers with the same value, they are collapsed down into a single
  • * comma-delimited list.
  • * ---
  • * From the spec - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
  • * ---
  • * >>> Multiple message-header fields with the same field-name MAY be present
  • * >>> in a message if and only if the entire field-value for that header field
  • * >>> is defined as a comma-separated list [i.e., #(values)]. It MUST be
  • * >>> possible to combine the multiple header fields into one "field-name:
  • * >>> field-value" pair, without changing the semantics of the message, by
  • * >>> appending each subsequent field-value to the first, each separated by a
  • * >>> comma. The order in which header fields with the same field-name are
  • * >>> received is therefore significant to the interpretation of the combined
  • * >>> field value, and thus a proxy MUST NOT change the order of these field
  • * >>> values when a message is forwarded.
  • * ---
  • *
  • * @output false
  • */
  • public struct function getNormalizedHeaders( required struct headers ) {
  •  
  • var normalizedHeaders = {};
  •  
  • for ( var name in headers ) {
  •  
  • normalizedHeaders[ name ] = getNormalizedHeader( headers[ name ] );
  •  
  • }
  •  
  • return( normalizedHeaders );
  •  
  • }
  •  
  •  
  • /**
  • * I return a single, normalized header value that ensures that complex values are
  • * collapsed down into a single, comma-delimited list.
  • *
  • * @output false
  • */
  • public string function getNormalizedHeader( required any value ) {
  •  
  • if ( isSimpleValue( value ) ) {
  •  
  • return( value );
  •  
  • }
  •  
  • // If we get this far, the given header value is complex and need to be collapsed
  • // down. The ColdFusion documentation states that this value will be an array:
  • // ---
  • // "If there is more than one instance of a header type, the type values are put
  • // in an array."
  • // ---
  • // But, it's not. It's actually a FastHashTable; so, we have to convert it from a
  • // struct to an array so that we can easily collapse it down into a list.
  • var valuesCollection = [];
  •  
  • for ( var pseudoIndex in value ) {
  •  
  • arrayAppend( valuesCollection, value[ pseudoIndex ] );
  •  
  • }
  •  
  • return( arrayToList( valuesCollection, ", " ) );
  •  
  • }
  •  
  • </cfscript>

When we run the above code, we get the following page output:


 
 
 

 
 Normalizing cfhttp headers to ensure that they are all simple values, even when there are multiple headers with the same name. 
 
 
 

I'm normalizing the header values by collapsing like-named headers into a single comma-delimited list. According to the HTTP spec, you should be able to do this; but, some reading that I've done suggests that the effectiveness of this depends on the context in which the headers are being consumed. I'm definitely not an HTTP protocol expert by any stretch of the imagination, so take this all with a grain of salt.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.