Reading Environment Variables In ColdFusion
The other day, when I was experimenting with the default credential provider chain in the Amazon Web Services SDK (Software Developer's Kit), I had to do something that I've never done before - read environment variables from ColdFusion. Since ColdFusion is built on top of Java, however, reading environment variables is rather straightforward once you have access the System object.
The System object (java.lang.System) exposes the method .getenv(). This method can be called with or without an argument. If you call it without an argument, it will return a Struct of all the environment variables (available to the ColdFusion user). If you call it with an argument, it will return the value associated with the given variable name (or null if there is no such environment variable).
Based on my brief experience, the Struct returned from the .getenv() is a bit different than most structs in ColdFusion. It is documented as an "unmodifiable string map." But, the behavior that I'm concerned with is case-sensitivity. If you access the returned struct using dot-notation (ex, environment.foo), the key appears to be case-insensitive. However, if you access the returned struct using array-notation (ex, environment["foo"]), the key appears to be case-sensitive.
If you call .getenv() with a key, that key also appears to be case-sensitive. And, they keys always appear to be upper-case; though, due to my lack of *nix experience, I am not sure if that is a convention or a requirement.
To demonstrate the use of the java.lang.System object, I've put together a small demo that reads the "TERM" property from the environment:
<cfscript> // Get access to the system class. system = createObject( "java", "java.lang.System" ); // Calling getenv() without an argument returns a map / struct of all of the // keys available in the current environment (to the ColdFusion user). environment = system.getenv(); // Calling getenv() with a specific key returns the value, or null if the given // key doesn't exist. // -- // CAUTION: The name of the environmental property is case-sensitive. value = system.getenv( javaCast( "string", "TERM" ) ); // Test the output of the values. // -- // CAUTION: The environment map, unlike most maps in the ColdFusion ecosphere, is // case-sensitive. Well, sort of. If you access the map using dot-notation, then it // is case-insensitive (ex, environment.term); however, if you access the map using // array-notation, then it is case-sensitive (ex, environment[ "TERM" ] ), and the // casing must match exactly. // -- // WORKS FINE: environment[ 'TERM' ] // THROWS ERROR: environment[ 'term' ] // WORKS FINE: environment.term // -- writeOutput( "TERM: #environment.term# <br />" ); writeOutput( "TERM: #value# <br />" ); </cfscript>
As you can see, I'm accessing the "term" value using both the environment struct as well as the specific key-based accessor. When we run the above code, we get the following output:
When I played around with this on Unix (Ubuntu), I was able to set environment variables by adding them to the /etc/environment file. However, on my Mac, I couldn't figure out how actually set an environment variable that pertained to the ColdFusion user. I'm sure it's doable - it's just beyond my understanding of the underlying systems.
Want to use code from this post? Check out the license.
Re this: "it will return a Struct".
You allude to this further down, but it *doesn't* return a struct, it returns a java.util.Collections$UnmodifiableMap (https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableMap-java.util.Map-).
I presume you're suggesting what's returned is a struct because it shows up in blue when you dump it, and it says "struct" at the top. This is misleading. I dunno the minutiae of it, but I think that any object which implements [some interface] will be displayed as a "struct" in a dump.
This does not make it a struct.
This - I suspect - is yet another instance of Adobe trying to be helpful by hiding stuff they reckon might "confuse" the CF dev, but really only adding to the confusion by misreporting things. Not helpful.
But a handy article in general, Ben. It's the first CF-oriented blog article I've read for a while!
Definitely, I was using "struct" in the generic sense, but I can see this as misleading. In my defense, I did try to clarify it a bit, talking about it as an "unmodifiable string map" and exploring case-sensitivity. But yeah, definitely not a Struct in the strict sense.
Anyway, just happy to have Mr. Cameron back on my site :D
Not sure if anyone told you, but you got a fun shout-out in the Lucee keynote at dev.Objective(). The Lucee CEO was talking about joyous moments in life, like when he first discovered ColdFusion and when "Adam Cameron discovered the Adobe Bugbase". It was a good chuckle - sorry we didn't get to see you this year.
@Ben, that was actually Matt Gifford in the day-1 keynote who made that funny reference. His slides (which were mostly animated Gif memes) showed a screenshot of all the tickets Adam has submitted.
Speaking of Lucee, I think the latest version actually incorporates the environment variables into the server struct for stuff just like this.
Oh snap, I'm mixing up my keynotes - thanks for the correction :D And that's cool to hear about Lucee making it more accessible.
Nice article! I was bugged when I first started learning ColdFusion that I couldn't easily get at the environment variables, but I guess I learned to deal with my disappointment. Using this idea with onServerStart() might be handy in some cases, say if there are environment variables that would be commonly used in applications.
It is my understanding that the uppercase environment variables in Unix are convention.
the interface that coldfusion will see as a "struct" when dumping a variable is java.util.Map, The interface that triggers the "array" dump is java.util.List.
Nice one @Tom.
The only caution with saving stuff in the Server scope is that if you change the ENV variables, you'd have to restart ColdFusion - or overwrite the server values somehow. Honestly, I don't have too much experience with the server scope, so I don't know what the good practices around it are. I believe you can point to an "onServerStart" event handler somewhere; but, I've never used it.
Agreed. I think that running onServerStart() wouldn't help, since CF would still have the same environment.
I work on intranet applications. We do find using server scope helpful in our environment. Essentially the variables defined there are treated as constants, and we all have to play nice to keep it that way.
That sounds like a good use of it.
@Barry and @Ben,
You could just have a .cfm file that you would call manually to refresh the server scope without requiring a CF restart. You would also include that in onServerStart for initial setting.