umegusa's blog

備忘録

revelフレームワークを使ってTwitterAPIを叩く!

golangフレームワークrevelを使用してTwitterAPIを叩きます。

アプリケーションの作成

revelフレームワークでまずはアプリケーションを作成しましょう!

$ revel new twitterApp

そうすると$GOPAT/src/twitterAppが作成されるはずです。
※ revelフレームワークの導入はこちらから。

revelフレームワークを入れてみる - umegusa's blog

Routingの設定

まずはルーティングを設定します。
今回はこのように設定しました。
$GOPATH/src/twitterApp/conf/route

GET     /                                       App.Index
GET     /auth                                   Auth.Index
GET     /auth/callback                          Auth.Callback
GET     /show                                   Show.Index

動きとしては、

URL 動作
http://host/ AppコントローラのIndexアクションメソッドを呼び出す
http://host/auth/ AuthコントローラのIndexアクションメソッドを呼び出す
http://host/auth/callback/ AuthコントローラのCallbackアクションメソッドを呼び出す
http://host/show/ ShowコントローラのIndexアクションメソッドを呼び出す

こんな感じに設定します。
それではコントローラをいじってみましょう。
$GOPAT/src/twitterApp/app/controller/app.go

package controllers

import "github.com/revel/revel"

type App struct {
	*revel.Controller
}

func (c App) Index() revel.Result {
	return c.Render()

/でアクセスするとこちらのIndex()メソッドが呼び出されます。
返り値としてはrevel.Resultを返します。
returnで返しているのは、views下のhtmlファイルを返します。
この場合、$GOPAT/src/twitterApp/app/views/App/Index.html を返します。
基本的に$GOPAT/src/twitterApp/app/views/{コントローラ名}/{アクションメソッド名}.html を返すみたいですよ。
中のHTMLは標準を少しだけいじりました。
Index.html

 <div class="container">
    <div class="row">
      <div class="hero-text">
        <h1>Twitter認証するよ!!</h1>
        <p><a href="/auth/">Twitterで認証するよ!</a></p>
      </div>
    </div>
  </div>

実行してみる

とりあえず実行するとこんな感じです。
実行方法は下記のように行います。

$ revel run twitterApp

画面だとこんな感じです。
http://localhost:9000/
f:id:umegusa:20150306011641p:plain
$GOPATH/src/twitterApp/conf/app.conf下をいじっていなければ9000番のポートで実行できるはずです。
続いてOAuth認証を実装してみましょう。

oauth認証をする

TwitterのOAuthのバージョンは1.0で、それを簡単にしてくれるライブラリを使用します。
それがこちらです。
mrjones/oauth · GitHub

実際に使用する前にまずはクライアントアプリケーションを登録し、consumerkey, consumersecretを取得する必要があります。
クライアントの登録は下記のページから作成できます。
Twitter Application Management
作成した際は必ずCallback URLを必ず設定してください。(設定しないとoauth_callbackパラメータが無効になり、コールバックが無効になってしまいます)
値はURLの形であれば適当で問題ありません(ex: http://twitter.com/kaisou4537/)

では実際に使ってみます。
controller下にauthコントローラを作成します。
auth.go

package controllers

import (
	"encoding/json"
	"github.com/mrjones/oauth"
	"github.com/revel/revel"
	"twitterApp/app/models"
)

type Auth struct {
	*revel.Controller
}

// oauthのコンシューマ設定
var twitter = oauth.NewConsumer(
        // 先ほど取得したキーを使用する
	"consumerkey",
	"consumersecret",
	oauth.ServiceProvider{
		AuthorizeTokenUrl: "https://api.twitter.com/oauth/authorize",
		RequestTokenUrl:   "https://api.twitter.com/oauth/request_token",
		AccessTokenUrl:    "https://api.twitter.com/oauth/access_token",
	},
)

func (c Auth) Index() revel.Result {
	// Twitterから情報を取得する
	user := getUser()

	// callback URLを設定する
	requestToken, url, err := twitter.GetRequestTokenAndUrl("http://localhost:9000/auth/callback")
	if err == nil {
		// ユーザ情報セット
		user.RequestToken = requestToken
		// oauth_verifierを取得
		return c.Redirect(url)
	}
	revel.ERROR.Println("リクエストトークン取得できませんでした!!", err)

	return c.Render()
}

func (c Auth) Callback(oauth_verifier string) revel.Result {
	// セットしたユーザ情報取得
	user := getUser()

	// access_tokenを獲得
	accessToken, err := twitter.AuthorizeToken(user.RequestToken, oauth_verifier)
	if err == nil {
		// ユーザ情報を取得する
		resp, _ := twitter.Get(
			"https://api.twitter.com/1.1/account/verify_credentials.json",
			map[string]string{},
			accessToken,
		)
		defer resp.Body.Close()
		account := struct {
			Name            string `json:"name"`
			ProfileImageURL string `json:"profile_image_url"`
		}{}
		_ = json.NewDecoder(resp.Body).Decode(&account)

		// 表示用情報をセット
		setUserData(account.Name, account.ProfileImageURL)
	} else {
		// 失敗
		revel.ERROR.Println("取得失敗!!", err)
	}

	return c.Redirect(Show.Index)
}

func (c Auth) Show() revel.Result {
	// ユーザ情報取得
	user := getShowUser("kaisou_test")
	return c.Render(user)
}

// Twitterユーザ情報
func getUser() *models.User {
	return models.FindOrCreate("kaisou")
}

// 表示用ユーザ情報セット
func setUserData(name, imgURL string) {
	models.CreateShowUser(name, imgURL)
}

~/app/models 下に下記の二つのファイルも作成します。
userinfo.go

package models

import "github.com/mrjones/oauth"

// Twitter用ユーザ情報
type User struct {
	Username     string
	RequestToken *oauth.RequestToken
	AccessToken  *oauth.AccessToken
}

var db = make(map[string]*User)

func FindOrCreate(username string) *User {
	if user, ok := db[username]; ok {
		return user
	}
	user := &User{Username: username}
	db[username] = user
	return user
}

showuser.go

package models

// 表示用ユーザ情報
type ShowUser struct {
	Username string
	ImgURL   string
}

var dbShowUser = make(map[string]*ShowUser)

func CreateShowUser(name, imgURL string) {
	user := &ShowUser{Username: name, ImgURL: imgURL}
	dbShowUser[name] = user
}

func FindShowUser(name string) *ShowUser {
	if user, ok := dbShowUser[name]; ok {
		return user
	} else {
		return nil
	}
}

auth.goでは、まずリクエストトークンを取得します。
その後、設定したコールバックURLにアクセスさせ、アクセストークンを取得、成功したらAPIをたたくという流れになります。

auth.goで取得したデータをリクエストトークンを格納、アクセストークンを取得するのに必要になります。
そして、アクセストークンを使用して取得したTwitterの情報をshowuser.goに格納し、View側のHTMLに渡してあげます。

auth.goのCallbackメソッドの最後にShowコントローラのIndexアクションメソッドにリダイレクトしていますね。
show.goは下記のようになっています。

package controllers

import (
	"github.com/revel/revel"
	"twitterApp/app/models"
)

type Show struct {
	*revel.Controller
}

func (c Show) Index() revel.Result {
	// ユーザ情報取得
	user := getShowUser("kaisou_test")
	return c.Render(user)
}

// 表示用ユーザ情報取得
func getShowUser(name string) *models.ShowUser {
	return models.FindShowUser(name)
}

auth.goで格納した情報を取得し、Viewに渡しています。
プログラムで処理した後のViewの値はRenderメソッドに渡してあげることでViewで扱えるようになります。
尚、使用する場合は下記のように呼び出します。
~/app/views/Show/Index.html

<div>
  <img src="{{.user.ImgURL}}">
  <p>{{.user.Username}}</p>
  <p>めっちゃ小さいけどとれてるね!!!!!</p>
</div>

実行して表示してみると画像のようになります。
f:id:umegusa:20150306011645p:plain

ユーザ情報が取得され、表示されているのが分かりますね!
これでTwitterAPIをたたいてユーザ情報を取得することができました!

終わりに

こんなにめんどくさいことしなくてもDBにaccesstoken格納してAPI呼び出せるようにしとけばええやん、という突っ込みは今回はなしでお願いします。。
とりあえずテスト的に取ることができました、ぱちぱち。
APIにアクセスして値を取ることができればTeitterAPIを使用したアプリケーションの作成は攻略したも同然なので、
あとはどのようなサービスにするかだけ考えて実装すればいいんじゃないでしょうか!

はまったこととしては

  • importに未使用のパッケージを定義するとエラーになる
  • 変数などに格納して1度も使用しないとエラーになる
  • 読み込んでいるパッケージ群の中で同じメソッド名で定義していた場合はエラーになる

ということでした。。

厳しいと思いつつ、かなり厳密なプログラムが出来上がるのでとてもいいと思います(毎回プログラムが雑すぎるということなので注意しなければならないですね。。)

最後にソースコードをあげておきます。
kaisou4537/revelTwitterApp · GitHub

以上、長々とありがとうございました!