We're speaking at
CFUnited 2008:
CFUnited - The Premiere ColdFusion Technical Conference

Search

Calendar

SunMonTueWedThuFriSat
    123
45678910
11121314151617
18192021222324
25262728293031

Subscribe Enter your email address to subscribe to this blog. You'll receive an email when we write a new post.

Recent Entries Come On In, Rails-The Water's Warm
Shan's Simple Examples: File uploads with Flex and ColdFusion

Recent Comments Google Calendar API - Creating a new Calendar with ColdFusion
Steve Julian said: When and where are you going to post the finished CFC's ? Thanks [more]

Three Phases of Programmer Development
Pat Branley said: I normally think of those phase 2 people as 'programmers' and the phase 3 people as 'developers'. I... [more]

New Job Title: Front End Engineer
Sean Corfield said: Well, there's always the excellent Fusion Authority Quarterly Journal... [more]

Down To The Wire: HTTP Sniffers
Brian M said: I second the mention of the Charles Web Debugging Proxy that Tariq mentioned. It is fantastic. It s... [more]

New Job Title: Front End Engineer
Patrick said: Heya Sean. Good point. I never understood how they did things over there at SysCon, and I understand... [more]

Archives By Subject Business of Software (4) [RSS]
ColdFusion (318) [RSS]
Conferences (6) [RSS]
Databases (87) [RSS]
Flex & Flash (109) [RSS]
Fusebox (87) [RSS]
General Development (29) [RSS]
Google (9) [RSS]
Hardware (5) [RSS]
JVM & Java (132) [RSS]
Linux (20) [RSS]
Miscellaneous (254) [RSS]
Performance (8) [RSS]
SeeFusion (36) [RSS]
Shan's Simple Examples (6) [RSS]
User Interface (3) [RSS]
Windows (5) [RSS]

Archives By Poster Daryl Banttari (10)
Nat Papovich (29)
Patrick Quinn (36)
Shannon Hicks (22)
Steve Nelson (21)
Tyson Vanek (3)


bottom corner

Google Calendar XML: Deciphering the Data

In my previous post we looked at getting a list of calendars using the Google API. We looked at how to make the request, but not what to do with the data Google sends back.

When you make a request for a list of calendars, Google sends the list back in an xml format. Some of it is fairly self explanatory but some of it is a bit of a mystery. For that matter I'm still not 100% certain what all of it is for, but I want to focus your attention on one area of data that is important.

First do this:

<cfinvoke component="GoogleCalendar" method="getCalendars" returnvariable="getCalendars">
   <cfinvokeargument name="subSessionToken" value="abcdefgYOURSUBSESSION">
   <cfinvokeargument name="gsessionid" value="abcdefgYOURGSESSION">
   <cfinvokeargument name="includeAllCalendars" value="0">
</cfinvoke>

<cfdump var="#getCalendars#">

This is assuming you've been following along with my other posts. When you run that code above it will display a data dump of the xml returned from Google.

Take a look at the "entry" sections. These are the individual calendars. (This is also assuming you've created at least one calendar) There should be an entry for each calendar. Drill down a little further and take a look at the various links, each calendar entry should have four links.

alternate - This URL gives you a list of all the entries for the calendar.
accessControlList - This URL gives you a list of who can modify the calendar
self - This URL provides the same basic information that you're looking at now, except for ONLY one calendar
edit - This URL is what you use for editing the entries in a calendar

I think what you'll find is that this is a very incomplete list of what you can do with the calendars. So personally i find this list to be a bit silly. For example, it doesn't display the URL for batch processing. Of course in the docs it will say things like "send a DELETE request to the calendar's edit URL" which is a pretty lame explanation. These URLs should be in the docs, not in the XML data.

What SHOULD be in this XML is the calendar ID. It's there, just hidden. If you look at each of those URLs you'll see the same string, most likely a URL-Encoded version of your gmail address, although not always. Basically on the end of each of those URLs is the ID for the calendar. If you look a little above the "link" values you'll see an "ID" value. But it's not just the calendar's ID, it's a full URL! Google doesn't give you the basic ID directly (because that would be too easy!) you have to extract it out of the "ID" URL. No, it's not hard, just messy.

That calendar ID (not the URL, the actual ID!) is important for our other methods. Here's how to extract it from the "ID" URL:

<cfloop from="1" to="#arraylen(getCalendars.feed.entry)#" index="count">
<cfoutput>#listlast(filelist.feed.entry[count].id.xmltext,"/")#</cfoutput>
</cfloop>

So that will loop over each calendar and display the calendar's actual ID for use with the other API methods. Now that we have the calendar ID, next we'll look at editing a calendar.

Goog Luck! -Steve Nelson

Google Calendar API: Using ColdFusion to get a list of Calendars

Before we handle the methods for editing a calendar, we need to make sure we're editing the correct calendar. To do that we need to get a list of the calendars from Google. This method we'll look at today is very simple. So, let's jump right into the code.

<cffunction name="getCalendars" >
      <cfargument name="subSessionToken" type="string" required="Yes">
      <cfargument name="gsessionid" type="string" required="Yes">
      <cfargument name="includeAllCalendars" type="boolean" default="true">
      <cfset var local=structnew()>
      <cfif arguments.includeAllCalendars>
         <cfset local.whichCalendars="allcalendars">
      <cfelse>
         <cfset local.whichCalendars="owncalendars">
      </cfif>
      <cfhttp url="http://www.google.com/calendar/feeds/default/#local.whichCalendars#/full?gsessionid=#arguments.gsessionid#" method="get" redirect=false>
         <cfhttpparam type="HEADER" name="Authorization" value="AuthSub token=#arguments.subSessionToken#">
      </cfhttp>
      
      <cfxml variable="local.getCalendars"><cfoutput>#cfhttp.filecontent#</cfoutput></cfxml>
      
      <cfreturn local.getCalendars/>
   </cffunction>

We start with our usual two login arguments of a "subSessionToken" and "gSessionId". These tokens tell Google whos calendars to get. I added a third token to this method called "includeAllCalendars". This argument is a boolean true or false value. If it is set to false it will only include calendars that are 'owned' by the user. Whereas true will show all calendars the user has access to. So for example, if a coworker shares her calendar with you, you won't 'own' that calendar, but you'll have access to it.

The next section is a simple cfhttp GET. The final section is just parsing the XML that Google returns. That's it, we're done.

I'll do another post to examine the data that Google returns, it's important to understand it. I'll be back shortly I need to run to the chiropractor. Get it? back... oh forget it.

-Steve Nelson

Google Calendar API - Creating a new Calendar with ColdFusion

Now that we have finished with the crazy method of obtaining all the various tokens for accessing the Google API, let's jump into our first Google Calendar API method. We will start by using the Google Calendar API to create a new calendar. This method is very straight forward. It's just a matter of correctly formatting the XML and sending a post request with the packet to the Google Calender API.

<cffunction name="newCalendar" >
   <cfargument name="token"/>
   <cfargument name="gsessionid"/>
   <cfargument name="title"/>
   <cfargument name="summary"/>
   <cfargument name="timezone" default=""/>
   <cfargument name="color" default="##2952A3"/>
   <cfset var atomxml="">
   <cfset var filelist="">
   <cfsavecontent variable="atomxml">
      <cfoutput>
      <?xml version='1.0'?>
      <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005' xmlns:gCal='http://schemas.google.com/gCal/2005'>
         <title type='text'>#arguments.title#</title>
         <summary type='text'>#arguments.summary#</summary>
         <cfif len(arguments.timezone)><gCal:timezone value='#arguments.timezone#'></gCal:timezone></cfif>
         <gCal:hidden value='false'></gCal:hidden>
         <gCal:accesslevel value='owner'/>
         <gCal:color value='#arguments.color#'></gCal:color>
      </entry>
      </cfoutput>
   </cfsavecontent>
   
   <cfhttp url="http://www.google.com/calendar/feeds/default/owncalendars/full?gsessionid=#arguments.gsessionid#" method="post" redirect=false>
      <cfhttpparam type="header" name="Authorization" value="AuthSub token=#arguments.token#">
      <cfhttpparam type="header" name="Content-Type" value="application/atom+xml">
      <cfhttpparam type="body" value="#trim(atomxml)#">
   </cfhttp>
   
   <cfif isxml(cfhttp.filecontent)>
      <cfxml variable="filelist"><cfoutput>#cfhttp.filecontent#</cfoutput></cfxml>
      <cfreturn filelist/>
   <cfelse>
      <cfdump var="#cfhttp#">
      <cfthrow detail="xml parsing error">
   </cfif>
</cffunction>

The first two arguments I would hope you recognize by now. The "token" argument is the AuthSubSessionToken explained here, remember it's the second token, not the first. The gSessionId is explained here.

The next 4 arguments: title, summary, timezone and color are all used for the XML packet we'll send to the Google Calendar API. Title and summary are self explanatory, timezone is actually a non-standard text string. If you search Google you'll be able to find a list of the various strings. I'm in the EST timezone but my Google timezone is "America/New_York". Finally the color argument is simply a HEX string for the color you want the calendar to show up as in the Google UI.

We take those arguments and create an atom xml packet with the values and we send the packet to the "owncalendars" feed. Notice on the end of the URL is the gSessionId. If you don't include that Google will give you a 403 error. Next notice that the AuthSub token is in cfhttpparam. You're going to see that in every cfhttp request we make from now on. Finally take a look at the last cfhttpparam. That's the atom+xml packet we just created. You'll notice it's a type="body" which is likely another cfhttpparam you've never used.

Google will either respond correctly with an xml packet or something got messed up and it'll throw an error. If they send back a correct xml packet, we parse it and return the parsed XML data. Otherwise we throw an error.

So that's it. If you haven't already, create a GoogleCalendar.cfc file and add the function above to it and try it out for yourself. Also make another file called TEST_googleCalendar.cfm and add this to it, change the token and gSessionId:

<cfinvoke component="GoogleCalendar" method="newCalendar" returnvariable="newCalendar" >
   
<cfinvokeargument name="token" value="CPPpjYXkBxCA64je%5Fv%5D%5F%4F%5F8B%0B"> <!--- Use the GoogleAuthenticate.cfc to get this value --->
   <cfinvokeargument name="gsessionid" value="K%6DF9QBlomks"> <!--- Use the GoogleAuthenticate.cfc to get this value --->
   <cfinvokeargument name="title" value="Test Calendar"/>
   <cfinvokeargument name="summary" value="This is my first CF based calendar"/>
   <cfinvokeargument name="timezone" value="America/New_York"/>
</cfinvoke>
<cfdump var="#newCalendar#">

Give that a shot and tell me if you have any problems with it.

Good luck! -Steve Nelson

Google API: Using gSessionId with AuthSub Part 4

I swear this is a bug. It has to be a bug because it's so ridiculous. If it's not a bug, I hope someone can explain it.

After you finish part 1 and part 2 of logging in using Google's AuthSub. You'll find it's just not enough. Something really weird happens if you attempt to get a list of calendars.

You get an HTTP 302 error. An HTTP 302 error is a redirect error. In a nutshell it's as if they have a cflocation on the first line of their Application.cfc file (translate that into whatever language Google is using). If you automatically redirect on a page that you send a post request to, all the headers are lost. In other words, everything gets screwed up. So here's how to fix it...

Add this code to the GoogleAuthenticate.cfc file:

<cffunction name="getCalendarSessionId" >
   <cfargument name="token" type="string"/>
   <cfset var gsessionid="">
   <cfhttp url="http://www.google.com/calendar/feeds/default/owncalendars/full" method="post" redirect=false>
      <cfhttpparam type="header" name="Authorization" value="AuthSub token=#arguments.token#">
      <cfhttpparam type="header" name="Content-Type" value="application/atom+xml">
   </cfhttp>
   <cfif cfhttp.responseheader.status_code is "302">
      <cfset gsessionid = listlast(cfhttp.responseheader.location, "=")>
   </cfif>
   <cfreturn gsessionid/>
</cffunction>

Are you confused yet? The first question to ask is... What is the token argument? Is it the first token or the second token?

Use the second token. If you really want to understand why, ask and I'll explain.

So all we're doing here is attempting to get a list of calendars. The cfhttpparam header passing in the token tells Google which user's calendars to get. It's going to fail and that's ok, we're expecting it to.

It's going to return an http 302 error. That 302 error will give us a URL to redirect the user to. On the end of the URL is a gSessionId. That's what we want. Strip off the gSessionId and cfreturn it.

This is the last of the Authsub steps you need to do. From here on out every Google API cfhttp request will include an AuthSub token in the header and the url includes: ?gsessionid=#arguments.gsessionid# Here's an example:

<cfhttp url="http://www.google.com/calendar/feeds/default/owncalendars/full?gsessionid=#arguments.gsessionid#" method="post" redirect=false>
<cfhttpparam type="header" name="Authorization" value="AuthSub token=#arguments.token#">
...

These two things tell Google who's data to get. Again, I swear this gSessionId is a bug. In my opinion, there should be no reason you need BOTH an Authorization token AND a sessionId. They're programmatically redundant. But, tough luck, you need them both.

Tomorrow we'll switch gears and start looking at the Google Calendar API itself.

-Steve Nelson

Google API: Logging in with AuthSub Part 3

Remember that "next" variable that we passed to google in my previous post? That's the URL that google will send our users to after they go through the approval process on Google.com. They have to do two things, first login to Google.com, then either grant or deny your request to access their google data. If they grant you access it will redirect them back to the next URL. But just so you know, if they deny your request, it will STILL give them the "next" link to click on, but it will not pass a token back to your page. That may throw an error if you're expecting it.

Once we have that token, we need to make a request for a SubSessionToken. Yes, we need ANOTHER token just so you'll be confused. Here's the code for the SubSessionToken. Let's discuss that first before we tie it all together.

<!--- Second Function in GoogleAuthenticate.cfc --->
<cffunction name="AuthSubSessionToken" returnType="string" >
   <cfargument name="token"/>
   <cfhttp url="https://www.google.com/accounts/AuthSubSessionToken" method="GET">
      <cfhttpparam type="HEADER" name="Authorization" value="AuthSub token=#arguments.token#">
   </cfhttp>
   <cfif cfhttp.responseheader.status_code is "403">
      <cfdump var="#cfhttp#">
      <cfthrow detail="Token revoked! Either you passed in an incorrect token or the user manually revoked it.">
   <cfelse>
      <cfreturn trim(listlast(cfhttp.filecontent,"="))>
   </cfif>
</cffunction>

The first thing to pay attention to is the cfargument passed in. This gets confusing very quickly. But you'll grasp it. This token argument is the FIRST token passed back from our Previous token request. This will make sense in a minute when I tie this all together.

The next thing to notice is the cfhttpparam line. You're probably used to type formfield and url. Maybe even cookie. I'm not surprised if you've never used type="header" before. Don't worry about it, few people do. I'm not exactly sure why Google does, it's possibly a performance thing. Who knows? Anyway, the syntax above works.

The final section is the responseheader.status_code. A value of 403 means the token was revoked and you have to go through the first steps again. Your users can revoke your token from Google.com (click on "my account" then "authorized websites") Or your token will be revoked if you screw up the request. Anyway, if it's not a 403 we want to parse out the SubSessionToken out of the filecontent and return that value. A simple listlast will do the trick.

Let's tie this all together. For simplicity sake, we'll call this file1.cfm, file2.cfm and GoogleAuthenticate.cfc

<!---file1.cfm--->
<cfinvoke component="GoogleAuthenticate" method="AuthSubRequest">
   <cfinvokeargument name="next" value="http://#cgi.http_host#/file2.cfm">
   <cfinvokeargument name="scope" value="http://www.google.com/calendar/feeds">
   <cfinvokeargument name="secure" value="0">
   <cfinvokeargument name="session" value="1">
</cfinvoke>

Notice the next variable is pointing to file2.cfm (below)

<!---file2.cfm--->
<cfinvoke component="GoogleAuthenticate" method="AuthSubSessionToken" returnvariable="SubSessionToken">
   <cfinvokeargument name="token" value="#url.token#">
</cfinvoke>
<cfoutput>#SubSessionToken#</cfoutput>

There you go. That's not so hard is it? Play around with it and see if you can get it to work. We now have completed getting the two necessary tokens. Tune in tomorrow to learn about the undocumented (but vitally necessary) gSessionid! Same bat place!

After i cover the gSessionId, I'll explain putting this into use with the calendar then the spreadsheet. That's when it gets fun.

-Steve Nelson

Google API: Logging in with AuthSub Part 2

The first step of the AuthSub proxy process is very simple. Which is good. It'll ease you into using the Google API. Below is the first function in my GoogleAuthenticate.cfc file. I'll publish the full file after I cover the individual functions. Feel free to build the CFC and follow along. Basically all you need to do is start with a file called GoogleAuthenticate.cfc, throw in some cfcomponent tags, then add the functions as I post them.

<cffunction name="AuthSubRequest" returnType="void" >
   <cfargument name="next" type="string">
   <cfargument name="scope" type="string">
   <cfargument name="secure" type="string">
   <cfargument name="session" type="string">
   <cflocation url=" https://www.google.com/accounts/AuthSubRequest?next=#urlencodedformat(arguments.next)#&scope=#urlencodedformat(arguments.scope)#&session=#iif(arguments.session,1,0)#&secure=#iif(arguments.secure,1,0)#" addtoken="No">
</cffunction>

In a nutshell it is one line of code. It's a cflocation. The two arguments to pay attention to are next and scope.

Next is the URL that Google will send the user back to after they login to google.com and accept your request to access their Google data. When you make use of this API you'll need to have the next attribute point to one of your own CFM page. Something like: http://www.yourdomain.com/whatever.cfm then in that whatever.cfm file you you need to save the #url.token# variable that Google returns to you.

Scope is a little simpler. Basically if tells Google which part of the API you want to access. I get the feeling there are LOT of 'scopes' (or eventually there will be a lot) but I have only found 3 so far:

1) http://spreadsheets.google.com/feeds
2) http://www.google.com/calendar/feeds
3) http://picasaweb.google.com/data/feed/api/user/victor.coustenoble?kind=album

I have tried the calendar and spreadsheet and it works great. I haven't tried the picasaweb (photo uploads)

Secure is either 1 or 0 I haven't even played with secure tokens. If anyone reading this has, explain the difference.

Session is also either 1 or 0 I've been setting session=1 because it allows you to save the token in a database and automatically log them back in later. It's a little more complex. In a nutshell you take this token, and get ANOTHER token called a subsession token. I'll explain that in another post.

So if you put this code into a file called: GoogleAuthenticate.cfc with cfcomponent tags. Here's an example in action:

<cfinvoke component="GoogleAuthenticate" method="AuthSubRequest">
   <cfinvokeargument name="next" value="http://127.0.0.1/next.cfm">
   <cfinvokeargument name="scope" value="http://spreadsheets.google.com/feeds">
   <cfinvokeargument name="secure" value="0">
   <cfinvokeargument name="session" value="1">
</cfinvoke>
Easy enough huh? I can't believe I just wrote a whole blog about one line of code (the cflocation). hahaha! beat that.

-Steve Nelson

Google API: The cool way to Login Part 1

I've become addicted to Google. The company does some truly wonderful things. Not perfect by any means, but wonderful. Lately I have been playing around quite a bit with the Google calendar API and the Google spreadsheet API. Also...logging into the API, which is important to understand. I've written a few CFCs to access all this available data. It was all kind of a pain in the ass, so I'm going to review my thought process and code for how it all works on our blog. I'll do it in small bite size chunks because some of it is wonderful, but just not perfect.

The webservice API has a centralized process for authenticating. But I'll say this... it's a bit complex. For the calendar there are 3 ways to login to the API.

1) Authsub Proxy
2) ClientLogin with username/password
3) MagicCookie

The magic cookie is where I started. Basically you login to your own calendar dig around in the manage calendars area, make a calendar public, get the magic cookie and then you can use CF to access the calendar data.

But... then I started thinking. This process is lame! To make use of this in a multi user environment I'd have to create a video about what links to click in the calendar UI. Then I'd have to setup a phone bank in India just to answer questions because someone clicked the wrong thing. That's no good.

So, then the next thing I tried was the ClientLogin process. This one feels natural. Basically you pass in a username and password via a web service and it returns a token (it's a little more complex than that). Then you pass that token around with each additional web service request.

But... then I started thinking. This is all well and good for my own Google account, but no educated web user is going to type in their Google username and password into someone else's application. I might as well ask them to use their social security number as a username!

So, I tried the last method of logging in. It's the Authsub proxy. It's wierd and a bit complex, but it's the solid solution for multi-user applications using the Google API. The details of making it work take a bit of explanation. In a nutshell the way it works like this:

1) You redirect a user to a Google.com page
2) They login to Google.com
3) They click a single button to authorize your website to use a specific part of the API (like the Calendar)
4) Then Google sends the user back to your application with an "authToken"

That's the way the documentation explains it at least. But, there are 2 more steps that aren't very clear. I'll show you with some code in another post.

5) Then you make ANOTHER request for a "subSessionAuthToken" (this is the token you REALLY want, it'll make sense when I explain it)
6) Wait wait... then you make ANOTHER request for a "gSessionId" (honestly... i think this is a bug)

Even though this seems like a complex process, for you the developer, it's quite simple for your users. Of the 3 methods, the AuthSub is the most powerful approach to authenticating into an API. Because your users don't have to give you their username and password for one. Secondly, if your users want you to stop accessing their Google data, they can login to Google and revoke your token. Lastly, you can store the token in your database and automatically authenticate them into the Google API from your application in the future.

The Google documentation explaining this is, well, fair at best. They have no ColdFusion examples which doesn't surprise me. After I do more posts with the CF code, I figure I'll send my code to Google to add to their docs. The Google API is wonderful, once you get it to work.

-Steve Nelson

Google Results via SMS, dial G-O-O-G-L-E

As Steve pointed out recently, Google has a phone/voice-based front-end to some of their search services. But did you also know that you can send an SMS text message to G-O-O-G-L-E (466453)? The best way to interact with it is to send a message like this:

97211 pizza

This will return all business results for "pizza" in the 97211 zip code. You'll get a text message (or two, for lots of listings) back in a few seconds which will include name, address, telephone number, etc. After sending a couple zip-coded messages, Google will tell you that you've set your geographic preference and subsequent messages don't need the zip code preface.

Anyone know if this works with the new cell-tower-triangulation that's been bandied about?

The Google 411

This is pretty slick. Basically you call 1-800-Goog-411 (1-800-466-4411) and it gives you an audio version of the Google maps search engine. You can search for local businesses then they'll redirect the call to them.

Apparently it sends you a text message or something if you say "Map it". I haven't tried this yet because for the last 6 months I've become a non-cellphone-using-fascist. Cell phones were draining my soul, but that's another story. From time to time I miss using one. Anyhoo, I hope someone reading this will give it a try and post a comment explaining how it works.

I saw a while ago that google now offers location pinpointing without GPS. In a nutshell They triangulate your position based the cell towers you are connected to. It's not extremely accurate, but close enough for getting directions. I'm suspect Goog-411 makes use of that.

-Steve Nelson

bottom corner