Friday, November 18, 2011

Using an external api in a Go AppEngine program - urlshortener from Google APIs


This tutorial is almost exactly the same as http://golangtutorials.blogspot.com/2011/11/using-external-api-in-go-web-program.html, except that we will be doing the same program for the AppEngine. There are a few differences on how to make it work, so I shall, even at the cost of repetition for those who are coming directly to this page for reference, go over the technical essentials again. But I would urge you to read the above tutorial first.

Purpose of this program: do our own urlshortener application using the Google APIs, but using the Google AppEngine platform.

As in the other tutorial, to review what we know so far, it would be good to go over these other tutorials and links:
* analysing an AppEngine for Go web app: http://golangtutorials.blogspot.com/2011/10/analysing-google-appenginge-for-go.html
* goinstall: http://golangtutorials.blogspot.com/2011/10/go-packages-and-goinstall-creating-and.html
* templates: http://golangtutorials.blogspot.com/2011/06/go-templates.html
* the google-api-go-client: http://code.google.com/p/google-api-go-client/

Step 1: Ensure your environment is setup right

* Setup the environment variable GOPATH to point to a directory of your choice (if you haven’t done so already).
The external source code will be downloaded into this directory $GOPATH/src and the packages also will be installed under this directory $GOPATH/pkg.

Step 2: Install the APIs

* Get the APIs by issuing the following command:
goinstall google-api-go-client.googlecode.com/hg/urlshortener/v1
* This will download the source code for all the apis into $GOPATH/src.
So you should have a whole bunch of directories among which there are also
$GOPATH/src/google-api-go-client.googlecode.com/hg/urlshortener and
$GOPATH/src/google-api-go-client.googlecode.com/hg/google-api
* … and also install the compiled packages into $GOPATH/pkg/"machine_arch"/.
The AppEngine however does not use these installed packages. We will be using only the source code as shown in step 3.

Note: this step is different from other tutorial

Step 3: Create the directory structure and copy api source

AppEngine programs are run from a directory. So create a directory with a name of your choice - I have called my directory urlshrtnrprj. Into this directory copy the source files of the downloaded API starting with and including the google-api-go-client.googlecode.com directory. You can find this below $GOPATH/src. For this tutorial’s program you need only two source sub directories:
google-api-go-client.googlecode.com/hg/urlshortener and
google-api-go-client.googlecode.com/hg/google-api.

Note that the “examples" directory should be deleted even if the others remain. This is because this directory also has a .go file with the init() function which is used by the AppEngine to start the application, and if the directory is not deleted, this other program might be started by the AppEngine.

If you have other files or folders also like _obj, .6, etc., don’t worry as it won’t affect your program as they are recompiled (except the “examples" directory under google-api-go-client.googlecode.com/hg which has to removed).

I’ll list my directory structure thus far for your reference:
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/Makefile
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/urlshortener-gen.go
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/urlshortener-api.json
urlshrtnrprj/google-api-go-client.googlecode.com/hg/google-api/Makefile
urlshrtnrprj/google-api-go-client.googlecode.com/hg/google-api/googleapi.go

Step 4: Write the code that imports the API and uses it, and app.yaml configuration file

The API can now be imported using a statement similar to:
import ( urlshortener "google-api-go-client.googlecode.com/hg/urlshortener/v1")

I’ve created a directory called gocode for my .go files. You can name it whatever you want though. The entire .go code is given below. There are only two changes from the regular web app in the related tutorial - change the name of the package from main, and change func main() to func init().

urlshrtnrprj/gocode/urlshortener.go
package urlshrtnrpkg //has to be changed from main to something else

import (
    "fmt"
    "http"
    "template"
    urlshortener "google-api-go-client.googlecode.com/hg/urlshortener/v1" //import the installed package
)

func init() { // has to be changed from main to init
    http.HandleFunc("/", handleRoot)
    http.HandleFunc("/shorten", handleShorten)
    http.HandleFunc("/lengthen", handleLengthen)

    http.ListenAndServe("localhost:8080", nil)
}

// the template used to show the forms and the results web page to the user
var rootHtmlTmpl = template.Must(template.New("rootHtml").Parse(`
<html><body>
<h1>My URL Shrtnr</h1>
{{if .}}{{.}}<br /><br />{{end}}
<form action="/shorten" type="POST">
Shorten this: <input type="text" name="longUrl" />
<input type="submit" value="Give me short URL" />
</form>
<br />
<form action="/lengthen" type="POST">
Expand this: http://goo.gl/<input type="text" name="shortUrl" />
<input type="submit" value="Give me long URL" />
</form>
</body></html>
`))

func handleRoot(w http.ResponseWriter, r *http.Request) {
    rootHtmlTmpl.Execute(w, nil)
}

func handleShorten(w http.ResponseWriter, r *http.Request) {
    longUrl := r.FormValue("longUrl") //get the value of the text field with name longUrl in the form
    urlshortenerSvc, _ := urlshortener.New(http.DefaultClient) //use the default client that is available in the http package and create a new instance of the url shortener service
    url, _ := urlshortenerSvc.Url.Insert(&urlshortener.Url {
        LongUrl: longUrl,
    }).Do() // fill in the Url data structure with the given long url and call the service
    rootHtmlTmpl.Execute(w, fmt.Sprintf("Shortened version of `%s` is : %s", longUrl, url.Id))
}

func handleLengthen(w http.ResponseWriter, r *http.Request) {
    shortUrl := "http://goo.gl/" + r.FormValue("shortUrl") // get the value of the text field with name shortUrl in the form
    urlshortenerSvc, _ := urlshortener.New(http.DefaultClient)
    url, err := urlshortenerSvc.Url.Get(shortUrl).Do()
    if err != nil {
        fmt.Println("error: %v", err)
        return
    }
    rootHtmlTmpl.Execute(w, fmt.Sprintf("Longer version of %s is : %s", shortUrl, url.LongUrl))
}

You also need the application configuration file which is given below.
urlshrtnrprj/app.yaml
application: urlshrtnrchk
version: 0-1-test
runtime: go
api_version: 3

handlers:
- url: /.*
  script: _go_app

At this point my directory contents are
urlshrtnrprj/app.yaml
urlshrtnrprj/gocode/urlshortener.go
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/Makefile
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/urlshortener-gen.go
urlshrtnrprj/google-api-go-client.googlecode.com/hg/urlshortener/v1/urlshortener-api.json
urlshrtnrprj/google-api-go-client.googlecode.com/hg/google-api/Makefile
urlshrtnrprj/google-api-go-client.googlecode.com/hg/google-api/googleapi.go

Step 5: Execute and test

To compile and execute the entire program via AppEngine, run dev_appserver.py giving your project directory as the argument. * To execute it I did: dev_appserver.py urlshrtnrprj (ensure that you do not have any other applications running on http://localhost:8080 or this step will fail)
* I will now to be able to navigate to our application web page: http://localhost:8080/.
You can test out your program now by entering a link like "http://www.google.com/" in the "Shorten this:" textbox. Clicking on the "Give me short URL" button should give you the result below.
Shortened version of `http://www.google.com/` is : http://goo.gl/fbsS

You can also try the reverse by entering "fbsS" in the "Expand this:" textbox and clicking on "Give me long URL" button. The response ought to be:
Longer version of http://goo.gl/fbsS is : http://www.google.com/

1 comment:

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.