2011年6月27日月曜日

Creating an original REST API using Google App Engine and Go (5)

2011/7/23追記: r58.1に伴うAPIの変更部分を修正

前回まででコメントの格納ができるようになった(っぽい)ので、今度はそれを取得できるようにして実際入ってることを確認する。

コメント取得用のAPI

まずはコメント取得用のAPIを作るところから考える。投稿用のAPIと同じ形を踏襲するので、特に深く考えることなく以下のURLにする。

http://localhost:8080/1/remark/get

あとはここのURLに対してのリクエストをハンドルする処理をサーバーサイドに追加していく。

サーバーサイド

そろそろ長くなってきたので、差分を載せていくことにする。下記がコメントの取得用APIを追加した部分のコード。

...
func init() {
    http.HandleFunc("/", errorHandler(root))
    http.HandleFunc("/1/remark/post", errorHandler(postRemark))
    http.HandleFunc("/1/remark/get", errorHandler(getRemark))
}
...

// Handle a get request of remarks
func getRemark(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    switch r.Method {
    case GET:
        c.Infof("Retrieving Remarks\n")

        // get all the remarks (new remarks come first)
        query := datastore.NewQuery(REMARK_KIND).Order("-Time")
        itr := query.Run(c)

        buf := bytes.NewBuffer(nil) // a variable sized buffer
        for {
            var remark Remark
            key, err := itr.Next(&remark)
            if err == datastore.Done {
                break
            } else if err != nil {
                http.Error(w, err.String(), http.StatusInternalServerError)
                break
            }
            c.Infof("Retrieved id:%d remark:%s", key.IntID(), remark.Content)
            fmt.Fprintf(buf, "id=%d&remark=%s\n", remark.Time, remark.Content)
        }
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        io.Copy(w, buf)
    default:
        msg := "Invalid request\n"
        c.Infof(msg)
        http.Error(w, msg, http.StatusBadRequest)
    }
}

処理の流れは以下の通り。重要なのは主にクエリをどう生成するか、という部分だろうか。

  1. コメント取得用のクエリを生成する
  2. クエリを実行して返ってきた結果を走査しつつ、レスポンス用にデータを整形する
  3. 適切なヘッダを付加してレスポンスを送り返す

ちょっとわかりづらいのはOrder("-Time")という部分で、ここは取得したデータのうち、Timeというフィールドの値の大小に応じて並びかえろと指定している。TimeというフィールドはRemark構造体に自分で勝手に定義したフィールドだから、別にここに来るフィールド名自体は何でもいい。あと、デフォルトでは昇順で動くので、頭にマイナス記号をつけて降順にしている。こうすることで、時間的には新しいコメントが先にくるよう並びかえられる、というわけ。

あと今は特に引数をあたえることなく決まった動作(全部のコメントを新しい順に取得する)しかしてないけど、getのAPI的にはもっと細かな制御ができるようにしないといけない。でもそれはひとまずあとまわし。

クライアントサイド

引き続きクライアントサイドのコード。こっちはまだ短いので全部載せる。

package main

import (
    "http"
    "fmt"
    "os"
)

func main() {
    client := new(http.Client)
    remarkUrlRoot := "http://localhost:8080/1/remark/"
    getUrl := remarkUrlRoot + "get"
    r, err := client.Get(getUrl)
    if err != nil {
        fmt.Printf("Error: %s",err)
        return
    }
    fmt.Println(r.Status)
    
    bufSize := 256
    buf := make([]byte, bufSize)
    for {
        read, err := r.Body.Read(buf)
        if read == 0 && err == os.EOF {
            fmt.Println("Finished reading")
            break
        } else if err != nil {
            fmt.Printf("Error during read: %s",err)
            break
        }
        // convert the buffer to a string and print it
        fmt.Println(string(buf[0:read]))
    }
    r.Body.Close()
}

指定されたURLにGetを発行して、返ってきたResponseのBodyを読みつつ表示する、という処理をやっている。決まりきった動作なので特に説明するところは無さそう。Goではこうやるんですよー的な見本かな。

出力結果

サーバーサイドとクライアントサイドの出力結果をそれぞれのせておく。ちゃんとクエリ通りの順番で取得できていることが確認できる。

サーバーサイドの出力結果

INFO     2011-06-26 15:19:20,629 __init__.py:324] building _go_app
INFO     2011-06-26 15:19:21,554 __init__.py:316] running _go_app
2011/06/26 15:19:21 INFO: Retrieving Remarks
2011/06/26 15:19:21 INFO: Retrieved id:1309056182 remark:aardvark
2011/06/26 15:19:21 INFO: Retrieved id:1309056155 remark:aardvark
2011/06/26 15:19:21 INFO: Retrieved id:1308993994 remark:aardvark
2011/06/26 15:19:21 INFO: Retrieved id:1308993976 remark:aardvark
INFO     2011-06-26 15:19:21,671 dev_appserver.py:4217] "GET /1/remark/get HTTP/1.1" 200 -

クライアントサイドの出力結果

200 OK
id=1309056182000000&remark=aardvark
id=1309056155000000&remark=aardvark
id=1308993994000000&remark=aardvark
id=1308993976000000&remark=aardvark

Finished reading

次回

PostしてGetするところまではできたから、残るは(REST的には)DeleteとPut。ということで、次はたぶん投稿したコメントを削除するDeleteのAPIをやるかな。

(続く)

0 件のコメント:

コメントを投稿