Golang Cafe #37、#37.1 まとめ Gmail API を試す。

2014/07/06に開催された「Golang Cafe #37」についてのまとめです。


今回は先日(密かに)Gmail APIをGo言語で試す回となりました。
Go言語でGoogle APIを使用するにはGoogle APIs Client Library for Goがあるのですが、先人の結果を見ると思わしい結果が得られなかったとのことなので直接APIを叩いてみることにしました。

事前準備

まずは、Google Developer ConsoleからGmail APIをONにします。
詳細な手順については、Golang Cafe #18 まとめ goauth2を試すにまとめてあるので参考にしてください。


次にgoauth2パッケージを導入します。
Gmail APIを使用するにはOAuth2による認証が必要になります。
Go言語用にはgoauth2が提供されているので、こちらを使用します。

$ go get code.google.com/p/goauth2/oauth

でインストールします。
こちらも詳細な使用方法はGolang Cafe #18 まとめ goauth2を試すにまとめてあるので参考にしてください。


※重要
APIの認可を取得する際、何種類かのレベルがあります。
今回は一番安全なhttps://www.googleapis.com/auth/gmail.readonlyで認可を取得しています。
フルアクセスの認可を取得した場合、誤ってメールを削除する可能性もあるので十分気をつけてください。

APIを実行する

ここではgoauth2による認証は完了しているという前提でまとめておきます。
今回は一覧の取得と、そこから得られたIDを元に1件の本文を取得する部分だけをまとめておきます。
というのもAPIの多さと、JSONに対応する構造体の定義が大変なため必要最低限の検証にしました。


今回検証したAPIは、


APIを実行した結果はJSONで得られるので、これに対応する構造体を定義しておきます。

type Response struct {
    Messages           []Message `json:"messages"`
    NextPageToken      string    `json:"nextPageToken"`
    ResultSizeEstimate uint      `json:"resultSizeEstimate"`
}

type Message struct {
    Id           string   `json:"id"`
    ThreadId     string   `json:"threadId"`
    LabelIds     []string `json:"labelIds"`
    Snippet      string   `json:"snippet"`
    HistoryId    uint64   `json:"hostoryId"`
    SizeEstimate int      `json:"sizeEstimate"`
    Raw          string   `json:"raw"`
    Payload      Payload  `json:"payload"`
}

type Payload struct {
    Body Attachments `json:"body"`
}

type Attachments struct {
    AttachmentId string `json:attachmentId`
    Data         string `json:"data"`
    Size         int    `json:"size"`
}

面倒ですが地道に定義するしかなさそうです。
注意する点はリファレンスのページに書いてある型をそのまま鵜呑みにするとエラーになります。
特に本文が入っているであろうMessage.RawやAttachments.Dataに関してはリファレンスのページでは[]byteと書いてありますが、stringで受けていないとjson.Unmarshalした際にエラーになります。


一覧を取得するサンプルコードです。

func list(client *http.Client, userAddress string) {
    url := fmt.Sprintf(
        "https://www.googleapis.com/gmail/v1/users/%s/messages?maxResults=10",
        userAddress)

    res, err := client.Get(url)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Get Error: %v\n", err)
        os.Exit(1)
    }

    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ReadAll Error: %v\n", err)
        os.Exit(1)
    }

    gres := Response{}
    err = json.Unmarshal(data, &gres)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unmarshal Error: %v\n", err)
        os.Exit(1)
    }

    for _, msg := range gres.Messages {
        fmt.Printf("%s,%d\n", msg.Id, len(msg.Raw))

        get(client, userAddress, msg.Id)

        time.Sleep(1 * time.Second)
    }

    fmt.Printf("%d件\n", len(gres.Messages))
}

関数の引数で渡されるclientですが、transport.Client()メソッドで取得できるhttpのClientになります。
こちらはgoauth2の認証が完了していれば、その認証済の情報がセットされた状態になっているので、GETなりPOSTなり通常通り使用するだけで大丈夫うです。
もう1つ注意点は、Users.messages: listで使用するuserIdですが、こちらは必ずhoge@gmail.comのように@gmail.comを必ず付けるようにしてください。


次に1件のメール本文を取得します。先の一覧で取得したMessage.Idを使用します。

func get(client *http.Client, userAddres, id string) {
    url := fmt.Sprintf(
        "https://www.googleapis.com/gmail/v1/users/%s/messages/%s?format=full",
        userAddres, id)
    // url := fmt.Sprintf(
    //     "https://www.googleapis.com/gmail/v1/users/%s/messages/%s?format=raw",
    //     userAddres, id)

    res, err := client.Get(url)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Get Error: %v\n", err)
        os.Exit(1)
    }

    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ReadAll Error: %v\n", err)
        os.Exit(1)
    }

    msg := Message{}
    err = json.Unmarshal(data, &msg)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unmarshal Error: %v\n", err)
        os.Exit(1)
    }

    if msg.Payload.Body.Data == "" {
        fmt.Println("empty")
    } else {
        b64, err := base64.URLEncoding.DecodeString(msg.Payload.Body.Data)
        if err != nil {
            fmt.Printf("Error0: %v\n", err)
        }

        fmt.Println(string(b64))
    }
    // if msg.Raw == "" {
    //     fmt.Println("empty")
    // } else {
    //     b64, err := base64.URLEncoding.DecodeString(msg.Raw)
    //     if err != nil {
    //         fmt.Printf("Error0: %v\n", err)
    //     }

    //     fmt.Println(string(b64))
    // }
}

urlでformat=fullにした場合は、Message.Payload.Body.Dataにbase64エンコードされたボディ部分が入ります。format=rawにした場合は、Message.Rawにbase64エンコードされたメール全体(ヘッダ+ボディ)が入ります。
それらをデコードすることで内容を得ることができます。


デコードの方法については、Go言語ではencoding/base64パッケージでbase64エンコードとデコードがサポートされていますが、StdEncodingではなくURLEncodingの方を使用してください。(http://golang.org/pkg/encoding/base64/#pkg-variables
両者の違いは、

StdEncoding A-Z、a-z、0-9および+/
URLEncoding A-Z、a-z、0-9および-_

のようです。
ちなみにGo言語ではbase64エンコード、デコード使用する文字を独自に定義することもできるようです。


またformat=rawにした場合は、mail.Messageパッケージを使用して解析すれば良いと思います。(すいません、まだ未検証です)
詳し手順はGolang Cafe #16 まとめ その1 mailパッケージを参考にしてください。

まとめ

躓くポイントは何箇所かりますが、今までGolang Cafeでやってきた内容で十分対応できそうです。
今回は基本的なところだけを試したましたが、同様にほかのAPIも実行できるのではないかと思います。
Google APIs Client Library for Goソースコードもみてみましたが、やはりJSONに対応した構造体の定義に苦戦しているようなコメントアウトした部分が多々ありました。


次回のGolang Cafeはなんらかのフレームワークを試してみるそうです。


※掲載しているソースコードは十分なエラー処理はなされていませんので、ご自身の判断で参考にしてください。いかなる問題が起きても当方は責任はおいません。

Golang Cafe #36 まとめ Google I/O 2014

2014/06/29に開催された「Golang Cafe #36」についてのまとめです。


今回はGoogle I/O 2014の中から公開されているGo関連の動画を見ていく予定でしたが、生憎公開されているものがなかったため、codelabの中からGoogle I/O 2014 CodeLabをやってみることになりました。


先日のGolana Cafe #34でも簡単にGAE/Gを復習しましたが、そのあたりに知識があれば進められるような内容になっています。
また、ここでもgorilla/muxが利用されていました。やはりgorilla/muxが標準になりつつあるのが感じられます。


今回は時間の都合上第3章までしか進められませんでしたが、残りも1度やってみることにします。


上記内容からは外れますが、今回も私の環境(Windows7 Pro 64bit)ではApp Engine SDKが上手く動作せずにハマってしまいました。
別のWindows7PCでは問題はないようなので何か環境に依存する部分があるとは思うのですが、別エントリにて応急処置の方法をまとめておきたいと思います。


もうしばらくの間は人柱となるべくWindowsで頑張ってみようと思います。

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ 読みました。

こちらにも読んだ本を記録しておこうと思います。
個々に読んだ本も随時追加したいと思います。

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

windows7 IIS WCF

一度まとめておきます。

IISのインストール

コントロール パネル→Windows の機能の有効化または無効化。
以下の機能をインストール

インターネット インフォメーション サービス
  Web 管理ツール
    IIS 管理コンソール
  World Wide Web サービス
    HTTP 共通機能
      HTTP エラー
      既定のドキュメント
    アプリケーション開発機能
      .NET 拡張機能
      ASP.NET
      ISAPI フィルター
      ISAPI 拡張機能
    セキュリティ
      要求のフィルタリング
    健全性と診断
      HTTP ログ

デフォルトで C:\inetpub\logs にログが出力されるので、IIS_IUSRS グループに変更、書き込み権限を追加。


管理者権限でコマンドプロンプトを起動して以下のコマンドを実行。

32bit版
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis -i

64bit版
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis -i

サンプルプログラム

WCFサービスアプリケーションを作成。
プロジェクトを右クリックして発行を選択

IISにデプロイ

Default Web Siteを右クリック→アプリケーションの追加

  • エイリアス:適宜(今回はwcfsample:大文字小文字注意)
  • アプリケーションプール:ASP.NET v4.0
  • 物理パス:サンプルプログラムを発行した場所


ブラウザで http://localhost/wcfsample/Service1.svc にアクセス。
サービスの内容が表示されればOK


サービスとしてホストする場合

http://msdn.microsoft.com/ja-jp/library/ms733069(v=vs.110).aspx

Golang Cafe #35 まとめ 続 Go Conference 2014 spring を読む。

2014/06/22に開催された「Golang Cafe #35」についてのまとめです。

今回も2014/05/31に開催されたGo Conference 2014 springの発表内容が公開されているので、こちらを読んでいくことになりました。


公開されている資料は+Yoshifumi YAMAGUCHI氏のブログから参照できます。
その中から以下のものを読んでみました。(順不同)


詳しい内容はそれぞれの資料を読んでいただくこととし、ポイントのみまとめておきます。

Five things that make Go fast

今回は第1章と2章だけ読みましたが、私達が実践できる技術的な内容ではなく、Go自体がいかにして速くしているかという内容でした。

コンパクトなデータ構造を作成し、キャッシュのうまく使い、パフォーマンスをあげている。
データ構造やCPUキャッシュの使い方のお話。

インライン化

可能な場合は関数のインライン化を行っている。


良くも悪くも内容的には上記の通り、Go言語を利用する上でこのようなバックグラウンドを知っておいても損はないかな、というような内容です。

光学ドライブのないノートPCでリカバリディスクを作成する

先日購入したVAIO(vpcz14AGJ)には光学ドライブが搭載されていません。
近年では当然ですがPCのリカバリはDtoDで行うようです。
リカバリディスク(DVD)等は自分で作成しておかなくてはならないのですが、残念なことに光学ドライブが搭載されていないので簡単には作成できません。


試行錯誤のうえ、なんとかリカバリディスクを作成することができたのでその手順を残しておきたいと思います。

実際に試した手順

スタートメニューから「VAIO Care」→「リカバリーと復元」→「リカバリー」→「リカバリメディアの作成」と進みます。


すると「ディスクドライブがありません」と怒られます。光学ドライブは付いていないので当然です。


物理的な光学ドライブがないのならDAEMON ToolsGizmo Driveのような仮想ドライブを使えばうまくいくのではないかと思いましたが、これらのソフトはあくまで読み込み専用でした。


そこで、イメージファイルに書き込むことができる仮想ドライブを作成するソフトはないかと探してみたところ、ありました。
TotalMounter
このソフトを使用すると、仮想ドライブを作成することができ、空のイメージファイルをマウントし、通常のDVDと同じように書き込むことが出来るようです。


インストール手順は通常のソフトと同じなので割愛します。(この手のソフトは余計なおまけソフトが混入しないように注意しましょう、このソフトは大丈夫そうです)


まずは、「Mountボタン」から「Virtual CD/DVD-RW」を選択します。


マウントするディスクのタイプと容量、イメージファイルの場所を入力します。
今回はリカバリ用のDVDを作成するので「DVD/Blu-Ray」、「Size:4700MBs」とし、イメージファイルの場所は適当です。


最初の画面に戻ると作成したイメージファイルが仮想ドライブとしてマウントされているのがわかります。


ここで改めてVAIO Careを起動して、リカバリメディア作成画面を表示します。すると今度はドライブがあるものと認識されリカバリメディアの作成が可能になります。



あとは手順にしたがって進めていくだけです。


と思っていたところ、思わぬ落とし穴に出くわしました。



無事に1枚目のメディアの作成が完了し、TotalMounterで2枚目の書き込み用イメージファイルをマウントし、次に進もうとしたところ、「次へ」ボタンが有効になりません。このソフトは光学ドライブの開閉を感知して処理を継続しているのかもしれません。これでは2枚め以降の作成ができません。


八方塞がりの状態で半ば諦めつつ、キャンセルしてVAIO Careを一旦終了して、再度「リカバリメディアの作成」を開くと、

「一部がすでに作成されているので続きから作りますか?」と!
さすがSONYさん!と(久しぶりに)思いました。一度でも(途中まででも)作成していれば続き(すべて作成していれば、任意のディスク)を作成できるようです。


早速2枚目のディスクを作成し、ディスクの入れ替えはできないので、一度VAIO Careを終了して、同じ手順で今度は3枚目のディスクを作成します。


これですべてのリカバリディスクのイメージを作成することができました。あとは書き込み可能な光学ドライブが付いているPCでDVDに書き込めば完了です。


1つ注意点として、VAIO Careは起動した時点でのドライブの情報を保持し続けるようです。
書き込みイメージ交換の際は「リカバリメディアの作成」だけでなく、「VAIO Care」自体を一度終了してから起動しなおしたほうがよさそうです。

免責事項

これはあくまで私が購入した中古のVAIO(VPCZ14AGJ、光学ドライブなし)にて成功した方法です。
すべてのPCにおいて同様の方法で作成できるかどうかはわかりません。
お試しされる方は個人の責任でお願いします。

Golang Cafe #34 GAE/G を触る 2014

2014/06/14に開催された「Golang Cafe #34」についてのまとめです。

Golang Cafeも<htmlday> 2014のイベントとして登録しているので、今回はweb寄りなお題ということで、GAE/Gを触ってみるということで、Go Tutorialを進めていきました。


私は以前に、

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)

を読んでいましたので、今回の内容で基本的な部分の復習をさせてもらいました。


詳細な内容はGo Tutorialを実際に読んでもらうとして、ポイントだけ整理しておきたいと思います。

The Development Environment

以前こちらにGolang Cafeを始める前の準備としてまとめて通りなのですが、Python2.7系(3系はダメです)とApp Engine SDK for Goが必要です。
それぞれダウンロードしてインストールしたディレクトリにパスを通しておきます。

Hello, World!

net/httpパッケージの使い方とよく似ています。
ハンドラをinitメソッドで登録するようにするのと、通常のGoのプログラムでは、

http.ListenAndServe(":8080", nil)

と自分で起動する部分を削除するだけです。

application: helloworld
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

versionは文字列でもなんでもいいようです。


チュートリアルでは、

myapp/
  app.yaml
  hello.go // ソースコードではpackage hello となっている

ディレクトリ構成にしていますが、Goの標準(?)にならって、

myapp/
  app.yaml
  hello/
    hello.go

としておくほうが間違いがないと思います。
ハンドラはすべてのファイルに書いてあるものが登録されるようなので、パスに対するハンドラを複数のファイルで重複して定義しないように注意する必要があります。


また、登録していないパスにアクセスされた場合、/(ルート)にフォワードされるようなので、未登録のパスに対しては404を返すなどの対応をしておく必要がありそうです。

Using the Users Service

GAE/GではGoogleユーザアカウントを使って認証することができます。twitterやFafebookでoauthを使って認証することもできますが、実装が非常に簡単です。

// コンテキストの作成
c := appengine.NewContext(r)
// 現在のユーザ情報の取得
u := user.Current(c)
// 未認証の場合はnilになります。
if u == nil {
    // ログインするためのurlを取得できます。
    url, _ := user.LoginURL(c, r.URL.String())

    ...
    // リダイレクト
} else {
    // ログアウトするためのurl
    url, _ := user.LogoutURL(c, "/")
}

Handling Forms

もちろんクライアントのフォームから送信した値を取得することができます。

func sign(w http.ResponseWriter, r *http.Request) {
    err := signTemplate.Execute(w, r.FormValue("content"))
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

html/templateパッケージを使用しているのでエスケープもされます。

Using the Datastore

データストアの基本的な使い方です。

// コンテキストの作成
c := appengine.NewContext(r)
// 登録するためのキーを作成
k := datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
key := datastore.NewIncompleteKey(c, "Greeting", k)
// 登録
_, err := datastore.Put(c, key, &g)

登録が失敗することもあるらしいので、その場合はタスクを使用するなど。

c := appengine.NewContext(r)
// 登録するためのキーを作成
k := datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
// クエリの作成
q := datastore.NewQuery("Greeting").Ancestor(k).Order("-Date").Limit(10)
greetings := make([]Greeting, 0, 10)
// 取得
if _, err := q.GetAll(c, &greetings); err != nil {

登録直後はGetAllでは取得できないのでキーを指定して取得するなど。
データストア特有の気をつけなくてはならない点が多くあるようです。

まとめ

今回は<htmlday> 2014のイベントということでしたので、生成されるページにHTML5を適用させておきました。
次回はGo Conference 2014 springの発表内容をもう少しだけみていくそうです。


GAE/Gに関しては、Golang Cafeに参加中の+TakashiYokoyama氏の書籍もあるのでご参考に。

Go言語プログラミング入門on Google App Engine

Go言語プログラミング入門on Google App Engine