Skip to main content
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Zeljka Majetic
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Zeljka Majetic ( @zeljkaNYC )

Normalizing HTTP Header Values Using CFHttp And ColdFusion

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel