Post a status update on twitter

Now that we know how to create a server, lets today post a status update on Twitter.

For posting to twitter we need an Oauth client library, to make our job easier. I have used go-oauth library.

Building on the previous post, we will add functionality which posts a tweet.

For posting the tweet we need to do the following

  • Register our application on twitter
  • Authorise our application to post a tweet on user’s behalf
  • Using the authorisation credentials to actually post a status update

Registering our application on twitter can be done here .

Now lets modify our main application to handle Oauth authorisation. We will start by updating the main function.

func main() {
	fmt.Println("Starting Server")
	http.Handle("/", &myHandler{handler: serveHomePage})
	http.HandleFunc("/authorize", serveAuthorizationPage)
	http.HandleFunc("/callback", serveOAuthCallback)
	http.Handle("/post", &myHandler{handler: postTweet})
	http.HandleFunc("/logout", serveLogout)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatalf("Error listening, %v", err)
	}
}

In the previous post we were simply serving a homePage at root (“/”), now other than serving the home page we have

  • “/authorize” url with serveAuthorizationPage handler.
  • “/callback” url with serveOAuthCallback handler.
  • “/post” url with postTweet handler.
  • and “/logout” url with serveLogout handler.

A thing to note here is the http.Handle() and http.HandleFunc() methods, In general http.HandleFunc() is an abstraction over the http.Handle() method, where we can directly pass the method without the ceremony of a custom handler (myHandler in this case).

Also, we will note (later) that wherever I am using http.Handle() I was not able to use http.HandleFunc(), since I was passing an additional parameter (third parameter) to the function other than http.ResponseWriter and *http.Request.

We are pretty good till now!

We updated serveHomePage() function, it now looks like this

func serveHomePage(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	if cred == nil {
		respond(w, homeLoggedOutTmpl, nil)
	} else {
		respond(w, homeTmpl, nil)
	}
}

Notice the *oauth.Credentials as third parameter, because of this I could not use http.HandleFunc() method. (Let me know, if there is a way I can use HandleFunc() here)

serveAuthorizationPage() page looks like this

func serveAuthorizationPage(w http.ResponseWriter, r *http.Request) {
	callback := "http://" + r.Host + "/callback"
	tempCred, err := oauthClient.RequestTemporaryCredentials(http.DefaultClient, callback, nil)
	if err != nil {
		http.Error(w, "Error getting temp cred, "+err.Error(), 500)
		return
	}
	putCredentials(tempCred)
	http.Redirect(w, r, oauthClient.AuthorizationURL(tempCred, nil), 302)
}

Here we specify a callback url (for twitter api to call back on) and request credentials using oauth client (go-auth). Here is the putCredential() function and the oauthClient variable

var oauthClient = oauth.Client{
	Credentials : oauth.Credentials{Token: "your app token", Secret: "your app secret"},
	TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token",
	ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize",
	TokenRequestURI:               "https://api.twitter.com/oauth/access_token",
}

func putCredentials(cred *oauth.Credentials) {
	secrets[cred.Token] = cred.Secret
}

Now lets take a look at serveOAuthCallback() the “/callback” handler

func serveOAuthCallback(w http.ResponseWriter, r *http.Request) {
	tempCred := getCredentials(r.FormValue("oauth_token"))
	if tempCred == nil {
		http.Error(w, "Unknown oauth_token.", 500)
		return
	}
	deleteCredentials(tempCred.Token)
	tokenCred, _, err := oauthClient.RequestToken(http.DefaultClient, tempCred, r.FormValue("oauth_verifier"))
	if err != nil {
		http.Error(w, "Error getting request token, "+err.Error(), 500)
		return
	}
	putCredentials(tokenCred)
	http.SetCookie(w, &http.Cookie{
		Name:     "auth",
		Path:     "/",
		HttpOnly: true,
		Value:    tokenCred.Token,
	})
	http.Redirect(w, r, "/", 302)
}

This method gets the Oauth_token provided by the api and stores them using putCredentials() method and sets a authentication cookie.

Now postTweet() method is actually the one which posts the tweet. It is invoked by “/post” url

func postTweet(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	var profile map[string]interface{}
	if err := apiPost(
		cred,
		"https://api.twitter.com/1.1/statuses/update.json",
		url.Values{"status": {"This is another test tweet sent from #golang application "}},
		&profile); err != nil {
		http.Error(w, "Error following, "+err.Error(), 500)
		return
	}
	respond(w, tweetedTmpl, profile)
}

In this method we hit the twitter api and post the tweet.

Here is how the complete program looks like

package main

import ("fmt"
	"net/http"
	"log"
	"text/template"
	"github.com/garyburd/go-oauth/oauth"
	"net/url"
	"io/ioutil"
	"encoding/json"
	"time"
)

var oauthClient = oauth.Client{
	Credentials : oauth.Credentials{Token: "your_app_token", Secret: "your_app_secret"},
	TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token",
	ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize",
	TokenRequestURI:               "https://api.twitter.com/oauth/access_token",
}

var (
	// secrets maps credential tokens to credential secrets. A real application will use a database to store credentials.
	secrets = map[string]string{}
)

type myHandler struct {
	handler  func(w http.ResponseWriter, r *http.Request, c *oauth.Credentials)
}

func serveHomePage(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	if cred == nil {
		respond(w, homeLoggedOutTmpl, nil)
	} else {
		respond(w, homeTmpl, nil)
	}
}

// serveOAuthCallback handles callbacks from the OAuth server.
func serveOAuthCallback(w http.ResponseWriter, r *http.Request) {
	tempCred := getCredentials(r.FormValue("oauth_token"))
	if tempCred == nil {
		http.Error(w, "Unknown oauth_token.", 500)
		return
	}
	deleteCredentials(tempCred.Token)
	tokenCred, _, err := oauthClient.RequestToken(http.DefaultClient, tempCred, r.FormValue("oauth_verifier"))
	if err != nil {
		http.Error(w, "Error getting request token, "+err.Error(), 500)
		return
	}
	putCredentials(tokenCred)
	http.SetCookie(w, &http.Cookie{
		Name:     "auth",
		Path:     "/",
		HttpOnly: true,
		Value:    tokenCred.Token,
	})
	http.Redirect(w, r, "/", 302)
}

func getCredentials(token string) *oauth.Credentials {
	if secret, ok := secrets[token]; ok {
		return &oauth.Credentials{Token: token, Secret: secret}
	}
	return nil
}

func deleteCredentials(token string) {
	delete(secrets, token)
}

func serveAuthorizationPage(w http.ResponseWriter, r *http.Request) {
	callback := "http://" + r.Host + "/callback"
	tempCred, err := oauthClient.RequestTemporaryCredentials(http.DefaultClient, callback, nil)
	if err != nil {
		http.Error(w, "Error getting temp cred, "+err.Error(), 500)
		return
	}
	putCredentials(tempCred)
	http.Redirect(w, r, oauthClient.AuthorizationURL(tempCred, nil), 302)
}


func postTweet(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	var profile map[string]interface{}
	if err := apiPost(
		cred,
		"https://api.twitter.com/1.1/statuses/update.json",
		url.Values{"status": {"This is another test tweet sent from #golang application "}},
		&profile); err != nil {
		http.Error(w, "Error following, "+err.Error(), 500)
		return
	}
	respond(w, tweetedTmpl, profile)
}

// apiPost issues a POST request to the Twitter API and decodes the response JSON to data.
func apiPost(cred *oauth.Credentials, urlStr string, form url.Values, data interface{}) error {
	resp, err := oauthClient.Post(http.DefaultClient, cred, urlStr, form)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return decodeResponse(resp, data)
}


// decodeResponse decodes the JSON response from the Twitter API.
func decodeResponse(resp *http.Response, data interface{}) error {
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("get %s returned status %d, %s", resp.Request.URL, resp.StatusCode, p)
	}
	return json.NewDecoder(resp.Body).Decode(data)
}

func putCredentials(cred *oauth.Credentials) {
	secrets[cred.Token] = cred.Secret
}


func respond(w http.ResponseWriter, t *template.Template, data interface{}) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := t.Execute(w, data); err != nil {
		log.Print(err)
	}
}

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var cred *oauth.Credentials
	if c, _ := r.Cookie("auth"); c != nil {
		cred = getCredentials(c.Value)
	}
	h.handler(w, r, cred)
}

// serveLogout clears the authentication cookie.
func serveLogout(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     "auth",
		Path:     "/",
		HttpOnly: true,
		MaxAge:   -1,
		Expires:  time.Now().Add(-1 * time.Hour),
	})
	http.Redirect(w, r, "/", 302)
}


var homeTmpl = template.Must(template.New("home").Parse(
`<html>
<head>
</head>
<body>
<a href="/authorize"> Authorize for Twitter</a>
<a href="/post">Post the tweet</a>
<a href="/logout">Logout</a>
</body>
</html>`))

var tweetedTmpl = template.Must(template.New("tweetedTmpl").Parse(
`<html>
<head>
</head>
<body>
 The post has been tweeted
<a href="/"> Home </a>
<br/>
<a href="/logout"> Logout </a>
</body>
</html>`))

var homeLoggedOutTmpl = template.Must(template.New("loggedOut").Parse(
`<html>
<head>
</head>
<body>
You are logged out
<a href="/authorize">Authorize</a>
</body>
</html>`))

func main() {
	fmt.Println("Starting Server")
	http.Handle("/", &myHandler{handler: serveHomePage})
	http.HandleFunc("/authorize", serveAuthorizationPage)
	http.HandleFunc("/callback", serveOAuthCallback)
	http.Handle("/post", &myHandler{handler: postTweet})
	http.HandleFunc("/logout", serveLogout)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatalf("Error listening, %v", err)
	}
}

A lot of code here has been taken from twitter example of go-auth

Happy tweeting!

~~ Whizdumb ~~
Email : sachin.xpert@gmail.com

4 responses

    • This is awesome, but for tutorials like this one I think is better to go with the full oath dance like he did so reader like me can use it with different services.

Comments are closed.