golangでoauth2を使ってFacebookAPIを叩いてみるよ!!
お前いつもoauth認証やってんな、という突っ込みもお待ちしております。
Facebookは未経験なのもあって結構てこずりました。
revelフレームワークを使ってfacebook認証っぽい何かを実装する
ぶっちゃけ使わなくても全然いけます(確信
いつものようにアプリケーションを作成しましょ。
revel new facebookApp
ルーティングなどは結構適当に作っていいと思います。
TwittweAPIとの違いはFacebookはoauth2を使って認証することです。
oauth2.0のほうが結構楽な気はしてます、毎回面倒な証明書作らなくていいみたいだし。
また、oauth2.0用のライブラリは1.0とは違うライブラリを使います。
golang/oauth2 · GitHub
公式なのかな?
goauth2を使ったやつは結構転がってたんですが、oauth2を使ったやつが全然なかったんですよね。。。
でもgoauth2 is deprecated.とあったのでoauth2を使うしかないような気がしてます。
goauth2 - Go OAuth 2.0 - Google Project Hosting
ということでoauth2を使って認証していくべし!!
認証自体は上記ライブラリを使えばめっちゃ簡単にできます。
ルーティングやらファイル構成は前回の記事を参考にしてみてください。
revelフレームワークを使ってTwitterAPIを叩く! - umegusa's blog
Facebookアプリの登録を行う
下記のサイトからアプリの登録を行います。
Facebook Developers
Developer登録はアカウント認証済みじゃないとだめっぽいので携帯電話の登録とか行いましょう。
作成したらlocalhostでもcallbackできるように設定します。
第一のつまずきポイント。
指定されたURLは、アプリケーションの設定で許可されていません。 指定されたURLの中にアプリの設定で認められないものが含まれています。 ウェブサイトまたはキャンバスのURLと一致しているか、ドメインがアプリのドメインのサブドメインである必要があります。
登録しないで認証しようとすると上記のように怒られます。
設定はアプリのDashBorad -> Settingsから設定します。
App Domainsにlocalhost, Add PlatformではURLにhttp://localhost/を設定します。
これで、アプリの設定は完了です。
FacebookAPIを叩く!
というわけで実装部分をみてみましょー。
今回は認証を通した人のメールアドレスを取得します。
auth.go
var facebook = newConfig() func newConfig() *oauth2.Config { c := &oauth2.Config{ ClientID: "xxxxxxxxxxxxxxxxxxxx", ClientSecret: "xxxxxxxxxxxxxxxxxxxx", RedirectURL: "http://localhost:9000/auth/callback", Endpoint: oauth2.Endpoint{ AuthURL: "https://www.facebook.com/dialog/oauth", TokenURL: "https://graph.facebook.com/oauth/access_token", }, Scopes: []string{"email"}, } return c }
最初にoauth2.Configを定義します。
ClientID,ClientSecretはクライアントアプリのAppID, Secretを指定してください。
んでまずはリクエストトークン(?)を取得しに行きます。
func (c Auth) Index() revel.Result { // facebookから情報を取得する url := facebook.AuthCodeURL("") return c.Redirect(url) }
AuthCodeURLメソッドを呼ぶとAuthURLで設定したURLが呼び出されます。
んでユーザ認証画面に飛ばされ、RedirecdURLに設定したURLにcallbackされます。
codeパラメータが付加されているので、それを受け取るアクションメソッドも用意します。
func (c Auth) Callback(code string) revel.Result { // codeを取得したらユーザ情報を取得 // アクセストークンを取得 tok, err := facebook.Exchange(oauth2.NoContext, code) // エラー処理は必須 if err != nil { return c.Redirect(App.Index) } // ユーザ情報を取得 client := facebook.Client(oauth2.NoContext, tok) result, err := client.Get("https://graph.facebook.com/me") if err != nil { // 失敗 revel.ERROR.Println("アクセストークン取得失敗!!", err) } // 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ defer result.Body.Close() //jsonをパースする account := struct { MailAddress string `json:"email"` }{} _ = json.NewDecoder(result.Body).Decode(&account) fmt.Println(account.MailAddress) // 取得したhtml(json)を確認する方法 // byteArray, _ := ioutil.ReadAll(result.Body) // fmt.Println(string(byteArray)) // htmlをstringで取得 return c.Redirect(Show.Index) }
Exchangeメソッドに帰ってきたコードを渡すことでアクセストークンを取得できます。
TokenURLに設定したURLにcodeパラメータを付加してアクセスしていますね。
err処理を実装しないとエラーでめっちゃ怒られます。
つまずきポイント2でした。
その後はトークンをhttpClientに突っ込んでアクセスすればオッケーです。
あとはencoding/jsonの処理で必要な値のみ取得して終わりです。
さくっとできますね!
途中deferでcloseを呼んでいますが、閉じる必要のあるものは必ず呼び出すようにしましょう。
実行するとアクセスログになんかメールアドレスが表示されているような気がします。
ちなみにFacebookも2.0で習得できるパーミッションの種類が大幅に削減されたみたいです。
- 公開プロフィール情報)public_profile)
- メールアドレス(email)
- いいね!リスト(user_likes
- 友達リスト(user_friends)
ちなみにこの情報はConfigのScopeに設定する項目になります。
上記を設定することで、使用するユーザにどの権限を使うかを明示的にします。
終わりに
Twitterに比べるとさくっと取れそうな感じですね。
GraphAPI全然勉強してないから何取れるか全然分からないけど
oauth2.0のほうがさくっと取れそうなのでSwiftとかでもチャレンジしてみたいっすね。
最近のサービスはoauth2.0採用してるところが多いので
他のもこんな要領で取れるのではないでしょうか。
今回のソースコードもべしっと。
kaisou4537/revelFacebookApp · GitHub
以上!
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/
$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>
実行して表示してみると画像のようになります。
ユーザ情報が取得され、表示されているのが分かりますね!
これでTwitterAPIをたたいてユーザ情報を取得することができました!
終わりに
こんなにめんどくさいことしなくてもDBにaccesstoken格納してAPI呼び出せるようにしとけばええやん、という突っ込みは今回はなしでお願いします。。
とりあえずテスト的に取ることができました、ぱちぱち。
APIにアクセスして値を取ることができればTeitterAPIを使用したアプリケーションの作成は攻略したも同然なので、
あとはどのようなサービスにするかだけ考えて実装すればいいんじゃないでしょうか!
はまったこととしては
- importに未使用のパッケージを定義するとエラーになる
- 変数などに格納して1度も使用しないとエラーになる
- 読み込んでいるパッケージ群の中で同じメソッド名で定義していた場合はエラーになる
ということでした。。
厳しいと思いつつ、かなり厳密なプログラムが出来上がるのでとてもいいと思います(毎回プログラムが雑すぎるということなので注意しなければならないですね。。)
最後にソースコードをあげておきます。
kaisou4537/revelTwitterApp · GitHub
以上、長々とありがとうございました!
ヒカルのgo#4に参加してきました!
golang触り始めてまだ1,2週間とかで
A Tour of Goを一通りみて演習課題をすっ飛ばしつつ進めていた私ですがgolangの勉強会に参加してきました。
今回参加してきたのはこちら。
ヒカルのgo! #4 (hikarie.go) - connpass
golang初心者〜中級者向け勉強会です。
会場の雰囲気と参加層
雰囲気はそんなに固い感じでもなく発表自体も初心者向けということで初心者でも割と理解できる内容でした。
golangも触ったことない人が半分以上でとりあえず聞いてみようの精神の人が多かったんじゃないかなーと。
懇親会はおいしいご飯食べながらだらだらしゃべる感じでいい雰囲気でした。
参加してみて
結論から言うとwebアプリケーション作成を行う場合は茨の道だなーという印象でした。
理由はDB処理がとても苦手ということ、ベストプラクティスがないことの二つです。
API開発とかには使えそうだけどこったWebアプリケーションの作成は修羅の道なるんじゃないかな、、、
ただ低レイヤーなアプリケーション(コンソールアプリケーションやらクライアントアプリケーションやら)などは活躍がかなり期待できそうな感じです。
Cっぽく書けそうだしそっちの人も入りやすい言語だよと言うお話を伺いました。
goも1.1が出てやっとメジャーになってきましたがまだノウハウなどは全然足りないような感じみたいです。
話題になっている言語なので盛り上げていきたいですよね!
発表内容
今回の勉強会では発表/LT/ハンズオン/懇親会の4部構成でした。
発表内容はこんな感じでした。
RDB
golangでSQLをたたくには?というお話でした。
結論としては銀の弾丸はない、ということでこれです!!という決定版がないことです。
これがあるのでDBの扱いは苦手なのかなと感じています。
goでSQLを扱う方法は主に3つあるみたいです。
- 生SQLをたたく
自分でSQLを作成してたたくという方法です。
取得した値を構造体に突っ込むんですが、そのマッピングなどは手動で行う必要があります。
使用するライブラリは標準のものになります。
- SQLクエリビルダーを使用する
生SQLよりも抽象度が高くなったものです。
マッピングは自身で行う必要がありますが、SQLの作成を助け、SQLをオブジェクトとして作成し、そのオブジェクトを渡して実行するという動きをします。
クエリの作成が簡単にでき、クエリのタイプミスを減らすことができたり、再利用可能なクエリを書き出したりできます。
ただし、非対応なSQL構文もあるみたいです。
- サブクエリ
- SELECT CASE
- UNION
これらに対応しているライブラリは現在ないそうです。
また、リレーション(JOIN)にも対応していないみたいなのですが、生のSQLも埋め込むことができるのでその辺の若干の手間が発生します。
それでも、かゆいところに届かないですが、クエリの作成、実行などは楽になりそうな気がしました。
squirrelというライブラリを使用すると下記のように書くことができます。
import sq "github.com/lann/squirrel" users := sq.Select("*").From("person").Where(sq.Eq{"name": []string{"hoge", "fuga"}) sql, args, err := users.ToSQL() // db.Query("SELECT * FROM person WHERE name IN (?,?)","hoge", "fuga")
ちなみにプレースホルダーもsquirrelならRDBMSに対応したエンコード、プレースホルダーに変換してくれるみたいです、便利
- ORM
ORMの機能を全て提供してくれているのは現在gormくらいしかないとのお話でした。
gormの機能として紹介されたのはこんな感じです。
- 唯一リレーションの解釈ができる
- マイグレーション
- soft-delete対応
中国の方が開発されたらしく、中国では商業利用もされているとか。
ただし、とても高機能でコード量が多く、変な動きをすることもあるというお話です。
ORMではないですが、テーブルマッピング(取得した値を構造体にマッピング)してくれるパッケージの紹介もありました。
- gorp
- xorm
- genmai
gorpの解説があったのですが、こちらは開発が停滞気味(更新が遅い)で、カジュアルにバグを踏むというお話でした・・・w
また、ORMではないので生SQLをたたく必要もあります。
結果として何がいいの?
めんどくさいかバグを踏むかの両天秤ということみたいです。
銀の弾丸はない、というのはこのことだったんですね・・・
マルチプラットフォームなインタラクティブシェルを楽になる
長くなってしまったのでさくっと。
goのREPL goreについてのお話でした。
gore便利だけどwindowsで使えないんだけどどうしよう・・・
→ 作ろう!ということで作ったみたいです。
内容はWindows APIをたたいて起動させているみたいですね。
golangを楽しく学ぼう!
まずはA Tour of Goを一通りやって基本機能のパッケージ化、他の言語をgoで翻訳することから初めて行こうぜというお話でした。
Goroutineとchannelとかは後ででも問題ないとのこと、まずは基本で他の言語でもできそうなところから!ということですね。
インターフェースを制するものはgoを制するともおっしゃっていました。
スクレイピングしよう!
ハンズオンです。
hikago-4_handsOn/text01.md at master · 7yan00/hikago-4_handsOn · GitHub
golangのドキュメントをスクレイピングしようと言う内容でした。
詳細はコードをのぞいてみてください!
終わりに
ついていけるか不安がありましたが、楽しく終えることができました。
勉強会はとても敷居の高い場所だと思っていましたが、情報共有の場とか交流を深める場としても使用できそうだなーという感想です。
とりあえず初心者向け勉強会出てみて雰囲気感じるというだけでもありだと思うので、今後も勉強会とかあったら参加していきたいなーと思える勉強会でした!
revelフレームワークを入れてみる
golangでrevelフレームワークというものを入れてみたのでメモ。
revelフレームワーク
The Revel Web Framework for Go
こちらのフレームワーク。
とりあえず入れるだけ入れて遊んでみます。
revelフレームワークのインストール
コマンド1発たたくだけです。
# go get github.com/revel/cmd/revel go: missing Mercurial command. See http://golang.org/s/gogetcmd package github.com/revel/cmd/revel imports github.com/agtorre/gocolorize imports github.com/revel/revel imports code.google.com/p/go.net/websocket: exec: "hg": executable file not found in $PATH
いきなり引っかかったやつです・・・・
よく見ると自分GOPATHの設定とか全然してなかったのでこちらを参考にしてパスを通す。
How to Write Go Code - The Go Programming Language
export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin
さらに言うとMercurialも入れてなかったのでHomebrewでインストールします。
# brew install mercurial
改めてぽちっとな
# go get github.com/revel/cmd/revel # revel ~ ~ revel! http://revel.github.io ~ usage: revel command [arguments] The commands are: new create a skeleton Revel application run run a Revel application build build a Revel application (e.g. for deployment) package package a Revel application (e.g. for deployment) clean clean a Revel application's temp files test run all tests from the command-line Use "revel help [command]" for more information.
入ったっぽいですね。
早速アプリケーション作ってみましょう。
# revel new myapp
そうすると作業ディレクトリ内にアプリケーションができる・・・
と思っていたのですが全然できていませんでした。
ファイルどこ?って探したら$GOPATH/src 下に作成されるみたいです。
# cd $GOPATH/src code.google.com/ github.com/ gopkg.in/ myapp/
はい、ありました。
早速実行してみましょう。
# revel run myapp
localhost:9000にアクセスするとIt's Works!と画面上に表示されています。
あとはカスタマイズしながらページ作成していけばいいんじゃないでしょうか。
ということでインストールメモでした。
githubからいろいろライブラリインストールしながら開発していくと思うので
一応そのへんの設定もできたということで開発捗りそうですね。
終わりに
readmeに設定とか説明とかフォルダ構造とか書いてあるのでそこ読んだりとか
サンプルアプリ読んでみて使い方覚えてみてから内部挙動追っかけようかなーと思ってます。
Nginx + Golang でWebアプリケーション開発を試してみた
NginxとGolangを使ってWebアプリケーションを作成してみたい!
とかそんなことを思い立ったのでチャレンジしてみました。
実行環境
OSのバージョンは下記の通りです。
サーバはサクラVPS借りてやってます。
# cat /etc/system-release CentOS release 6.6 (Final)
CentOS 6.6ですね。
Nginxのインストール
yumを使ってインストールします。
# yum install nginx
ここで落ちてきたのがバージョンが低いものでした。
ですのでアップデートします。
まずはnginx用リポジトリを作成します。
# vi /etc/yum.repo.d/nginx.repo
内容は下記のようになります。
nginx.repo
[nginx] name=nginx repo baseurl=http://nginx.org/packages/rhel/$releasever/$basearch/ gpgcheck=0 enabled=1
そしてアップデートします。
# yum update
最後にPATHを通しておきます。
.bashrcなどに書いておくといいかもしれません。
# export PATH=$PATH:/usr/local/nginx/sbin
Nginxのインストールは以上で完了です。
Nginxの設定
NginxでWebサーバの設定を行う
続いて設定を行います。
以前はApacheで動いていたのでごっちゃにならないように設定します。
まずサーバ起動時にNginxが立ち上がるようにします。
Apacheも同様の設定をしていたので、そちらの設定は解除するような形になりますね。
# chkconfig httpd off # chkconfig nginx on
完了したら今度はNginxを起動します。
# service nginx start
okが出たらアクセスしてみましょう。
画像のようにWelcome to Nginx!というページが出てくるはずです。
ページが表示されればOKです。
Golangが動くように設定を行う
yumでインストールすると
/etc/nginx/
の中にconfigファイルが量産されてるはずです。
serverの設定を下記のように書き換えます。
# vi /etc/nginx/conf.d/default.conf
一番簡易的な設定は下記のとおりになるのかな?
default.conf
server { listen 80; server_name hostname; location / { fastcgi_pass 127.0.0.1:9000; include fastcgi_params; } }
hostnameは自分のサーバのホストになります。
以上でNginx側の設定は完了です。
Golangのインストール
ダウンロードは下記ページでできます。
Downloads - The Go Programming Language
# wget https://storage.googleapis.com/golang/go1.2.2.linux-amd64.tar.gz # tar -C /usr/local -xzf go1.2.2.linux-amd64.tar.gz
インストールが完了したらPATHを通します。
こちらも.bashrcに書いておくと問題ないかもしれません。
export GOROOT=/usr/local/go export PATH=$PATH:/usr/local/go/bin
最後に実行できるか確認してみましょう。
# go -V
golangのバージョンなどが表示されていれば問題なくインストールされています。
GoでWebアプリケーションを作成する
今回はチュートリアルをほぼそのまま流用したものを使用して記述してみます。
index.go
package main import ( "fmt" "net" "net/http" "net/http/fcgi" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) } func main() { l, err := net.Listen("tcp", "127.0.0.1:9000") if err != nil { return } http.HandleFunc("/", handler) fcgi.Serve(l, nil) }
tcpソケットでListenして終わりです。
ポート番号などNginxで設定したものとあわせてください。
Webアプリケーションを実行する
今回は /var/golang/ 下に作成したプログラムを格納しました。(多分どこでも大丈夫だと思います)
作成したプログラムを実行します。
# cd /var/golang/ # go run index.go
もしくは
# cd /var/golang/ # go build index.go # ./index
で実行できます。
あとはプログラムで設定したURLにアクセスして、
表示されれば完了になります。
バックグラウンドで実行しておく
実行コマンドの後に & をつけることでバックグラウンドで実行されるようになります。
# ./index &
プロセスを殺すときはkillコマンドを実行しましょう。
# jobs # kill %job_number
以上です。
SwiftでTwitter OAuthを取得する access_token編
ついにTwitterAPIの認証部分をクリアしてタイムラインを取得するところまでできましたのでメモ。
ここまでできればあとは整えてクリアだ!!とか思ってたらまた壁にぶつかったので今日はここまでにしてこれまでの内容をまとめます。
oauth_tokenを使用してaccess_tokenを取得する
前回の記事ではrequest_tokenにパラメータを投げてトークンを取得するところまでやりました。
SwiftでTwitter OAuthを取得する oauth_token編 - umegusa's blog
今回はここの続きです。
request_tokenを取得して認証とaccess_tokenを取得するところまでやります。
まず、トークンを取得します。
ここは前回のコードを参照してください。
// 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) // Authorize self.authorizeClient(NSString(data: data, encoding: self.dataEncoding)!) }
トークンを取得したらAuthorizeClientメソッドに飛びます。
Authorizeは下記のように実装します。
// Authorize func authorizeClient(param: String) { var divideParam = param.componentsSeparatedByString("&") var oauthToken = divideParam[0].componentsSeparatedByString("=")[1] let queryURL = NSURL(string: self.authorize_url + "?oauth_token=\(oauthToken)") // safariで認証 UIApplication.sharedApplication().openURL(queryURL!) }
本当はWebViewを使って認証したかったのですが、今回はsafariを使用してそのまま認証させます。
Authorizeはrequest_tokenで取得したパラメータをgetメソッドで投げるだけです。
そうすると認証画面に飛ぶことができます。
ただし、ここでの注意点は下記2点です。
- アプリケーション作成時に何でもいいからコールバックURLを入力する
- これを入れないとoauth_callbackパラメータが無効になってしまう
- oauth_callbackパラメータはプロトコル部分だけだとうまくいかない
うまくいくとsafariが立ち上がって下記認証画面へ進み、アカウントで認証が成功したらアプリに戻ってきます。
よく見る画面ですね。
コールバックされるとoauth_token, oauth_verifierのパラメータが付与されたURLが返ってきます。
oauth-swift://hogehoge/?oauth_token=sakldsjalkdjdkljfasldf&oauth_verifier=klajlfhjagdkasgf
※ 上記パラメータはダミーです
カスタムURLでアクセスされた情報を取得する
AppDelegate.swiftに下記のようにapplicationメソッドを用意することで取得することが可能です
AppDelegate.swift
func application(application: UIApplication!, openURL url: NSURL!, sourceApplication: String!, annotation: AnyObject!) -> Bool { ViewController.handleOpenURL(url) return true }
今回はViewControllerにhandleOpenURLという静的メソッドを実装してそちらに飛ぶように実装しています。
(内容がひどいのでNSNotificationCenterをうまく使って実装したい。。。)
尚、静的メソッドは下記のように実装できます。
class func handleOpenURL(url: NSURL) { println("OK!!") }
class func メソッド名 で静的メソッドの実装ができます。
ここでoauth_token, oauth_verifierを使用してaccess_tokenを取得しにいきます。
access_tokenを取得する
ここまで来たらもう少しです!
access_tokenの取得方法は基本的にリクエストトークンの取得と同じです。
ただし、証明書の発行や送信に使用するパラメータに差異があります。
// get access token func postAccessTokenWithRequestToken(url: NSURL){ // 基本的にリクエストトークンと同じだが、リクエストに差異がある // ここで設定するのは下記パラメータ // - oauth_token // - oauth_verifier var param = Dictionary<String, String>() var query : String = url.query! var divideParam = query.componentsSeparatedByString("&") for paramStr in divideParam { var splitParam = paramStr.componentsSeparatedByString("=") param[splitParam[0]] = splitParam[1] } // oauth_param param["oauth_version"] = "1.0" param["oauth_signature_method"] = "HMAC-SHA1" param["oauth_consumer_key"] = self.consumer_key param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) param["oauth_signature"] = self.oauthSignatureForMethod("POST", url: NSURL(string:self.access_token_url)!, parameters: param) // アルファベット順に並べ替える var authorizationParameterComponents = urlEncodedQueryStringWithEncoding(param).componentsSeparatedByString("&") as [String] authorizationParameterComponents.sort { $0 < $1 } // リクエスト文字列作成 var headerComponents = [String]() for component in authorizationParameterComponents { let subcomponent = component.componentsSeparatedByString("=") as [String] if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: NSURL(string: self.access_token_url)!); request.HTTPMethod = "POST" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // カスタムヘッダ設定 request.setValue("OAuth " + ", ".join(headerComponents), forHTTPHeaderField: "Authorization") // 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) } }
上記により、access_token(パラメータ上はoauth_token, oauth_token_secret)を取得できます。
リクエストトークンの取得と差異があったのは先ほど取得したパラメータを使用するというところのみになります。
ここで取得したoauth_token, oauth_token_secretを使用して、TwitterAPIにアクセスします。
home_timelineを取得する
Twitter OAuth v1.1になって毎回カスタムヘッダを作成しなければ情報を取得できなくなってしまったみたいですね。
ということでタイムラインを取得するAPIを使用して取得してみましょう。
// タイムライン取得 func getUserTimeline(tokenParam: String) -> Void{ var param = Dictionary<String, String>() var divideParam = tokenParam.componentsSeparatedByString("&") for paramStr in divideParam { var splitParam = paramStr.componentsSeparatedByString("=") if(splitParam[0] == "oauth_token"){ self.access_token = splitParam[1] }else if(splitParam[0] == "oauth_token_secret"){ self.access_token_secret = splitParam[1] } } // 取得に必要なパラメータ // oauth_tokenが新規に追加される param["oauth_token"] = self.access_token param["oauth_version"] = "1.0" param["oauth_signature_method"] = "HMAC-SHA1" param["oauth_consumer_key"] = self.consumer_key param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) param["oauth_signature"] = self.oauthSignatureForMethod("GET", url: NSURL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!, parameters: param) // http header用パラメータ作成 // アルファベット順に並べ替える var authorizationParameterComponents = urlEncodedQueryStringWithEncoding(param).componentsSeparatedByString("&") as [String] authorizationParameterComponents.sort { $0 < $1 } // リクエスト文字列作成 var headerComponents = [String]() for component in authorizationParameterComponents { let subcomponent = component.componentsSeparatedByString("=") as [String] if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: NSURL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!); request.HTTPMethod = "GET" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // リクエスト設定 request.setValue("OAuth " + ", ".join(headerComponents), forHTTPHeaderField: "Authorization") // 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) } }
HMAC-SHA1でパラメータとキーを渡してSHA1ハッシュ値にしてbase64エンコードするところは同じなのですが、ユーザ情報を取得する場合、下記のような変更があります。
// signature作成 func oauthSignatureForMethod(method: String, url: NSURL, parameters: Dictionary<String, String>) -> String { // URLとパラメータをHMAC-SHA1ハッシュ値に変換してbase64エンコードする var signingKey : String = "\(self.consumer_secret)&" var signingKeyData = signingKey.dataUsingEncoding(dataEncoding) // access tokenを取得したらkeyにaccess token secretを付加する必要がある signingKey = "\(self.consumer_secret)&\(self.access_token_secret)" signingKeyData = signingKey.dataUsingEncoding(dataEncoding) // パラメータ取得してソート var parameterComponents = urlEncodedQueryStringWithEncoding(parameters).componentsSeparatedByString("&") as [String] parameterComponents.sort { $0 < $1 } // query string作成 let parameterString = "&".join(parameterComponents) // urlエンコード let encodedParameterString = urlEncodedStringWithEncoding(parameterString) let encodedURL = urlEncodedStringWithEncoding(url.absoluteString!) // signature用ベース文字列作成 let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" let signatureBaseStringData = signatureBaseString.dataUsingEncoding(dataEncoding) // signature作成 return SHA1DigestWithKey(signatureBaseString, key: signingKey).base64EncodedStringWithOptions(nil) }
consumer_key_secretだけでなく取得したoaut_token_secretも連結してキーに渡して証明書を作成する必要があります。
ここで引っかかって全然取得できなかったのは良い思い出です。。。
成功すると下記のようなjsonが返ってきます。
[ { "contributors": null, "coordinates": null, "created_at": "Sun Feb 15 09:09:26 +0000 2015", "entities": { "hashtags": [], "media": [ { "display_url": "pic.twitter.com/Jsu0oYxcGK", "expanded_url": "http://twitter.com/NIKEiD_JP/status/566884628070682624/photo/1", "id": 566884626841747456, "id_str": "566884626841747456", "indices": [ 139, 140 ], ...... } ], // 長いので省略 "favourites_count": 0, "follow_request_sent": false, "followers_count": 169270, "following": true, "friends_count": 15, "geo_enabled": false, "id": 1946049950, "id_str": "1946049950", "is_translation_enabled": false, "is_translator": false, "lang": "ja", "listed_count": 483, "location": "NIKE", "name": "NikeStoreJapan", "notifications": false, "profile_background_color": "131516", "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/378800000106403467/c19a3d26691f9eb4bb431ad3f87676ad.jpeg", "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/378800000106403467/c19a3d26691f9eb4bb431ad3f87676ad.jpeg", "profile_background_tile": true, "profile_banner_url": "https://pbs.twimg.com/profile_banners/1946049950/1418890257", "profile_image_url": "http://pbs.twimg.com/profile_images/472164680534732800/brtYH1Lj_normal.jpeg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/472164680534732800/brtYH1Lj_normal.jpeg", "profile_link_color": "009999", "profile_location": null, "profile_sidebar_border_color": "FFFFFF", "profile_sidebar_fill_color": "EFEFEF", "profile_text_color": "333333", "profile_use_background_image": false, "protected": false, "screen_name": "NikeStoreJapan", "statuses_count": 1393, "time_zone": "Tokyo", "url": "http://t.co/nKcfAyJlq0", "utc_offset": 32400, "verified": true } } ]
成功しましたね!
あとはjsonをパースして表示するだけです。
終わりに
一応home_timelineは取得できましたが、また次の壁に出会いました。
カスタムヘッダではなくAPIのパラメータを付加して何かする(つぶやくなど)ことがまだ実現できていません。
そのほか色々やることがまだまだある予感です。
- safariじゃなくて WebViewで認証画面出してもろもろ操作したい
- つぶやくなどパラメータを付加してデータ取得したい
- そもそもタイムライン並べてない(jsonパーサ作ろう)
- ソースコードももっと汎用性を持たせてライブラリ化しておきたい
などなど・・・
先は長そうですね・・・・
でも認証周りが成功したということはほぼ終わったも同然だと考えていますので、
さくさく進められたらいいなと思う今日この頃です!
SwiftでTwitter OAuthを取得する oauth_token編
Swiftやらプログラミングのモチベが結構高まってきてます。
以前から苦戦していたTwitter OAuth認証をSwiftでやりたいと思ってずっと試行錯誤してました。
今日やっとoatuh_tokenを取得できたので備忘録として残します。
Twitterアプリケーションの作成
https://dev.twitter.com/
ここのTOOLS -> Manage Your Appsへリンク辿ってクライアントを作成します。
必要なものはTwitterアカウントで、今から作成する場合は電話番号を登録しないと作成できないみたいです。
あとはCreateNewAppのボタンから必要な情報を入力して
クライアントを作成します。
callbackのアドレスを入力する欄があるのですが、
この欄に何かしら値を入れておかないと認証できないとか。
ですので今回はとりあえず自分のtwitterページを登録しておく・・・
カスタムURLスキーマを設定しておけばそこを無視してくれるので特に問題ないはず。
OAuth認証
OAuth認証がいまいち理解できなかったのでメモ。
ちゃんと後から勉強して正しい情報載せたいと思うけど、
とりまTwitterの認証フローを参考にまとめてみる。
クライアントを登録するとConsumer KeyとConsumer Secretの2つが発行される。
これを使ってoauth_tokenを取得し、access_tokenを取得してAPIにアクセスする。
request発行 -> oauth_token取得 -> access_token取得 -> apiアクセス
こんな流れ。
詳しいことはちゃんともろもろ取得してからその際に・・・
oauth_token取得
access_tokenを取得するためのoauth_token, oauth_token_secretを取得するプログラムを書いた。
画面側はとりあえずButtonと書かれたボタンをクリックするとトークンを取得するようにしてる。
最初のテンプレートはSingle View Application。
ViewController.swift
import UIKit import Foundation class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func clickButton(sender: AnyObject) { doTwitter() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Twitter OAuth認証 func doTwitter(){ let oauth = OAuthSwift( consumerKey: Twitter["consumerKey"]!, consumerSecret: Twitter["consumerSecret"]!, requestTokenUrl: "https://api.twitter.com/oauth/request_token", authorizeUrl: "https://api.twitter.com/oauth/authorize", accessTokenUrl: "https://api.twitter.com/oauth/access_token" ) // 通信開始 oauth.start() } }
OAuthSwift.swfit
import Foundation class OAuthSwift{ // oauthリクエスト参考 // http://developer.yahoo.co.jp/other/oauth/signinrequest.html // http://www.pressmantech.com/tech/programming/1137 var dataEncoding: NSStringEncoding = NSUTF8StringEncoding var data : NSMutableData? = nil var consumer_key: String var consumer_secret: String var request_token_url: String var authorize_url: String var access_token_url: String // コンストラクタ init(consumerKey: String, consumerSecret: String, requestTokenUrl: String, authorizeUrl: String, accessTokenUrl: String){ self.consumer_key = consumerKey self.consumer_secret = consumerSecret self.request_token_url = requestTokenUrl self.authorize_url = authorizeUrl self.access_token_url = accessTokenUrl } func start() -> Void{ // クライアントアプリケーションの作成 // 流れとして request_token投げる -> ouath_token取得(今ここ) -> authrize_request投げる -> access_token取得 -> API使用 // リクエストURL設定 let twitterURL : NSURL = NSURL(string: "https://api.twitter.com/oauth/request_token")! // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: twitterURL); request.HTTPMethod = "POST" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // oauth_request設定 var param = Dictionary<String, String>() // バージョン param["oauth_version"] = "1.0" // 証明書アルゴリズム(Twitterでは固定) param["oauth_signature_method"] = "HMAC-SHA1" // ConsumerKey param["oauth_consumer_key"] = self.consumer_key // Unixタイムスタンプ param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) // ランダムな文字列 param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) // コールバック param["oauth_callback"] = "oauth-swift://" // 証明書 param["oauth_signature"] = self.oauthSignatureForMethod("POST", url: twitterURL, parameters: param) // アルファベット順に並べ替える var authorizationParameterComponents = urlEncodedQueryStringWithEncoding(param).componentsSeparatedByString("&") as [String] authorizationParameterComponents.sort { $0 < $1 } // リクエスト文字列作成 var headerComponents = [String]() for component in authorizationParameterComponents { let subcomponent = component.componentsSeparatedByString("=") as [String] if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } // 最終的にリクエストするのは下記文字列 // "OAuth oauth_callback=\"swift-oauth%3A%2F%2Fswift-oauth%2F\", oauth_consumer_key=\"CXubzXLR2vzqbCf1d9maSJ4ob\", oauth_nonce=\"46B623F4\", oauth_signature=\"%2Brx8F1ofGHhQe0iN%2Ff7MArz05F4%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1422107013\", oauth_version=\"1.0\"" // リクエスト設定 request.setValue("OAuth " + ", ".join(headerComponents), forHTTPHeaderField: "Authorization") // 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) } } // サーバからレスポンスを受け取ったときのデリゲート func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) { // Recieved a new request, clear out the data object self.data! = NSMutableData() } // サーバからデータが送られてきたときのデリゲート func connection(connection: NSURLConnection!, didReceiveData data: NSData!){ self.data!.appendData(data) } // データロードが完了したときのデリゲート func connectionDidFinishLoading(connection: NSURLConnection!){ // バイナリデータが発行される let html : String = NSString(data: self.data!, encoding: NSUTF8StringEncoding)! // コンソールに出力 println(html) } // signature作成 func oauthSignatureForMethod(method: String, url: NSURL, parameters: Dictionary<String, String>) -> String { let signingKey : String = "\(self.consumer_secret)&" let signingKeyData = signingKey.dataUsingEncoding(dataEncoding) // パラメータ取得してソート var parameterComponents = urlEncodedQueryStringWithEncoding(parameters).componentsSeparatedByString("&") as [String] parameterComponents.sort { $0 < $1 } // query string作成 let parameterString = "&".join(parameterComponents) // urlエンコード let encodedParameterString = urlEncodedStringWithEncoding(parameterString) let encodedURL = urlEncodedStringWithEncoding(url.absoluteString!) // signature用ベース文字列作成 let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" let signatureBaseStringData = signatureBaseString.dataUsingEncoding(dataEncoding) // signature作成 return SHA1DigestWithKey(signatureBaseString, key: signingKey).base64EncodedStringWithOptions(nil) } // Dictionary内のデータをエンコード func urlEncodedQueryStringWithEncoding(params:Dictionary<String, String>) -> String { var parts = [String]() for (key, value) in params { let keyString = urlEncodedStringWithEncoding(key) let valueString = urlEncodedStringWithEncoding(value) let query = "\(keyString)=\(valueString)" as String parts.append(query) } return "&".join(parts) as String } // URLエンコード func urlEncodedStringWithEncoding(str: String) -> String { let charactersToBeEscaped = ":/?&=;+!@#$()',*" as CFStringRef let charactersToLeaveUnescaped = "[]." as CFStringRef var raw: NSString = str let result = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, raw, charactersToLeaveUnescaped, charactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(dataEncoding)) as NSString return result } // SHA1署名のハッシュ値を作成 func SHA1DigestWithKey(base: String, key: String) -> NSData { let str = base.cStringUsingEncoding(dataEncoding) let strLen = UInt(base.lengthOfBytesUsingEncoding(dataEncoding)) let digestLen = Int(CC_SHA1_DIGEST_LENGTH) let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen) let keyStr = key.cStringUsingEncoding(NSUTF8StringEncoding) let keyLen = UInt(key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)) CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyStr!, keyLen, str!, strLen, result) return NSData(bytes: result, length: digestLen) } }
はい、https://api.twitter.com/oauth/request_token にPOSTでリクエスト送るだけ。
ちょっと制限があって、query stringのパラメータ名が昇順じゃないといけないとかハッシュ値作成しなきゃいけないとか。
その辺はプログラムソース見てもらえれば、全部できたら改めて書き直すと思う。
これから
ライブラリ使えばこの辺一発でできるんだけどそれだと理解できないから作っただけ、自己満足。
とりあえずツイート取得、投稿、その他昨日を提供できるようにすることとOAuthTwitterを綺麗にしたりHTTPClient作ったりすることを進めていく。
あとは忘れないようにちゃんとこの辺の知見のまとめをこのブログに残しておく。
最低限これをやっていこうと思う。
近いうちにまずはツイート取得を目指していこう!