Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Reinbold
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Reinbold ( @libel_vox )

Download A GitHub Gist As JSON Using A Proxy End-Point In ColdFusion

By on
Tags:

UPDATE (2022-01-26): After deploying this proxy end-point, I noticed that an old Gist from years ago wasn't loading (returning a 404). The Gist ID was shorter (only 7-characters long, not 32-characters). After looking at the Gist detail and seeing the embed code, I ended-up changing the Gist URL to include my username:

https://gist.github.com/{ gistID }.json

... changed to:

https://gist.github.com/bennadel/{ gistID }.json

It seems that the username isn't necessary for the newer Gist IDs but is necessary for the older Gist ID formats. That said, both formats work perfectly well when the username is included.


About a decade ago, I started looking into hosting my code samples using GitHub gists. The entire impetus for this is that when you embed a gist, it's beautifully formatted with line-numbers and syntax highlighting. However, embedding a gist is rather strange in that it uses a JavaScript file to execute document.write() calls that render the Gist Stylesheet and the HTML markup. In order to load my Gists after the DOM (Document Object Model) is ready, I have to override the document.write() implementation in order to create a sort of man-in-the-middle attack to programmatically capture the Gist content. But, as of this morning, I'm no longer doing that - I'm loading the Gist as JSON (JavaScript Object Notation) using a proxy end-point in ColdFusion.

To embed a gist, you can reference a .js file that is based on the ID of the gist. Example:

https://gist.github.com/12345abcde.js

This will block-and-load a JavaScript file that has two document.write() calls in it: one for the Stylesheet and one for the HTML markup. Over the weekend, however, I learned from Miguel Piedrafita that you could retrieve a GitHub Gist as JSON (JavaScript Object Notation) by using a .json file extension instead of a .js one. Example:

https://gist.github.com/12345abcde.json

Unfortunately, when I attempted to load this JSON file using the fetch() API, I received a CORS (Cross-Origin Resource Sharing) error since the Gist API isn't including the necessary Access-Control-Allow-Origin HTTP header. What I need is to be able to load the Gist content from my own domain.

To get around the CORS issue, I created a super simple ColdFusion API end-point that does nothing but grab the Gist JSON and return it. And, since ColdFusion's File IO methods are really just an abstraction over inputs and outputs, I can actually use the fileReadBinary() function to read the contents of the remote API calls on GitHub:

<cfscript>

	param name="request.attributes.gistID" type="string";

	// Historically, when ColdFusion makes an HTTP request using the CFHttp tag, the
	// content sometimes shows up as a String and sometimes as a Byte Array (binary). As
	// such, I usually set the "getAsBinary" attribute to True in order to create a
	// normalized response. In this case, since I'm just using the File IO abstraction,
	// I'm creating this normalization through the fileReadBINARY() function.
	request.template.content = deserializeJson(
		charsetEncode(
			fileReadBinary( "https://gist.github.com/#request.attributes.gistID#.json" ),
			"utf-8"
		)
	);

</cfscript>

Here, I'm using the native fileReadBinary() function to implicitly make the API call to load the Gist JSON content. Then, all I'm doing is encoding the binary response as a UTF-8 string, deserializing the resultant JSON, and using it as my API response.

Of course, if this weren't part of my API subsystem, I could have written this a little more simply as:

<cfscript>

	param name="request.attributes.gistID" type="string";

	cfcontent(
		type = "application/x-json; charset=utf-8",
		variable = fileReadBinary( "https://gist.github.com/#request.attributes.gistID#.json" )
	);

</cfscript>

And that's literally how easy it is to make a simple JSON proxy end-point in ColdFusion!

With this GitHub Gist proxy end-point in place, I can easily load the Gist content as JSON without overriding the document.write() functionality - a definite upgrade in terms of cleanliness. Part of me wishes I wasn't using Gist at all - in a perfect world, I would completely host the rendered experience. But for now, this is a baby step towards a better solution.

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

Reader Comments

14 Comments

If I were you, I'd consider doing some validation to make sure that request.attributes.gistID matches the expected pattern and return an HTTP 403 otherwise.

Even if it's just a simple regex to match an alphanumeric string like

^[A-Za-z0-9]+$

Should be enough to keep some bad actor from funneling bad requests to github from your server's IP.

15,674 Comments

@Aaron,

Yes, great point! And, I think you're right on the RegEx - that should be sufficient for what I've seen in the Gist IDs. I'll make sure to get that in there tomorrow morning 💪 #TeamWorkForTheWin

14 Comments

@Ben,

Anytime. Like I said that basic regex should be sufficient. You could make it tighter if you found the actual spec for GIST IDs.

Having not put any thought into it before, I just assumed they were SHA1 -- like git commit IDs. But, GistID seem to be only 32 characters long instead of 40.

If the are really 32 character hex strings then this would work.

^[A-Fa-f0-9]{32,32}$
15,674 Comments

@Aaron,

I think you should just be able to have the single value, {32}. Hmm, I thought I approved all your comments - not sure why you needed to be moderated again. Maybe I pushed the wrong button - still working out all the kinks.

14 Comments

You're right on {32} ....

No worries on moderation. if IP address is a factor, I switched locations (coworking office -> home) in-between comments.

15,674 Comments

@Aaron,

Ahh, that's what it was. Yeah, it's IP related. I wasn't sure if I wanted to include IPs. My big concern was that a spammer could find an email address for a user, and then use their approval to start posting messages. Though, maybe I should wait until that problem happens before I solve for it 😛 Might not be an issue at all. I'll see if I can relax that constraint.

15,674 Comments

@Aaron,

Ok, I locked the cfparam tag for this down - changed the type to regex and added a pattern:

<cfscript>

	param
		name="request.attributes.gistID"
		type="regex"
		pattern="[a-fA-F0-9]+"
	;

</cfscript>

I love how freaking flexible ColdFusion is 😎

15,674 Comments

@All,

It seems that root-level Gist IDs work for newer gist values; but, older gists (with shorter IDs, less than 32-characters) don't work that way. In order to get both the older and newer gists to work, I had to change the URL structure from:

https://gist.github.com/{ gistID }.json

... to:

https://gist.github.com/bennadel/{ gistID }.json

Note the /bennadel/ path segment. This appears to work more consistently.

Post A Comment — I'd Love To Hear From You!

Post a Comment

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