2011年6月21日火曜日

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

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

だらだらと記事を書いている間にGoogle App Engine SDKの1.5.1がリリースされて、GoでもChannel APIがサポートされるようになったみたい。まぁそれはおいおいやっていくとして、とりあえず話を続ける。

サーバーサイドのサンプル

前回"http://localhost:8080/1/remark/send"に対してPOSTするAPIをとりあえず作ってみよう、ということになったので、早速サーバーサイドからコードを書いてみる。エラーハンドリングの部分はmostachioのサンプルから引っ張ってきてるので詳しくはそちらを参照。

package hello

import (
    "fmt"
    "http"
    "template"
    "appengine"
)

const (
    GET string = "GET"
    POST string = "POST"
    DELETE string = "DELETE"
    REMARK_FIELD = "remark"
)

var (
    errorTemplate  = template.MustParseFile("error.html", nil)
)

func init() {
    http.HandleFunc("/", errorHandler(root))
    http.HandleFunc("/1/remark/send", errorHandler(sendRemark))
}

func root(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "At root!")
}

// Handle a remark that was sent to the service
func sendRemark(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)

    switch r.Method {
    case POST:
        r.ParseForm()
        remark, present := r.Form[REMARK_FIELD]
        if !present { // required field does not exist
            c.Infof("Required field does not exist\n")
            w.WriteHeader(http.StatusBadRequest)
        } else { // Got required field
            c.Infof("Remark: %s\n", remark)

            // process the remark here...
        }
    default:
        w.WriteHeader(http.StatusBadRequest)
        c.Infof("Invalid request\n")
    }
}


// errorHandler wraps the argument handler with an error-catcher that
// returns a 500 HTTP error if the request fails (calls check with err non-nil).
func errorHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                w.WriteHeader(http.StatusInternalServerError)
                errorTemplate.Execute(w, err)
            }
        }()
        fn(w, r)
    }
}

キモはsendRemark関数の中で、処理としては以下のことをやっている。

  1. POSTかどうかを判定する。POSTじゃなかったらエラー。
  2. POSTだったら、"remark"というフィールドがちゃんと送られてるか確認する。フィールドが無ければエラー。
  3. remarkとして送られてきた内容を取得して、処理を行う。いまはまだログとして出力してるだけ。

クライアントサイドのサンプル

サーバーサイドの原型ができたところで、今度はクライアントサイドも作ってみる。こっちは別にGoでなくてもいいんだけど、せっかくなのでGoで書く。

package main

import (
    "http"
    "fmt"
)

func main() {
    client := new(http.Client)
    url := "http://localhost:8080/1/remark/send"
    data := http.Values { 
        "remark": {"aardvark", },
        "blah": {"blah", },
    }
    r, err := client.PostForm(url, data)
    if err != nil {
        fmt.Printf("Error: %s",err)
    }
    fmt.Println(r.Status)
}

内容としては、APIとして用意したURLに対して適切なフィールドと一緒にPOSTする、という極めて単純なコード。

出力結果

ということでサーバーをあげてクライアントを実行したときの両方の出力結果を見てみる。

サーバーサイドの出力

INFO     2011-06-21 14:01:03,593 __init__.py:324] building _go_app
INFO     2011-06-21 14:01:04,390 __init__.py:316] running _go_app
2011/06/21 14:01:04 INFO: Remark: [aardvark]
INFO     2011-06-21 14:01:04,501 dev_appserver.py:4217] "POST /1/remark/send HTTP/1.1" 200 -

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

$ ./client 
200 OK
$

remarkフィールド付きの適切なPOSTを送ったので、期待通りの結果が返ってきている。ここでもしremarkの部分が存在しなかったとしたら以下の感じになる。

サーバーサイドのエラー出力

2011/06/21 14:19:49 INFO: Required field does not exist
INFO     2011-06-21 14:19:49,131 dev_appserver.py:4217] "POST /1/remark/send HTTP/1.1" 400 -

クライアントサイドのエラー出力

$ ./client 
400 Bad Request
$

期待通り、ちゃんとエラーが返ってきている。

次のステップ

そんな感じで、POSTされた発言を取りだすところまではひとまずできた。次は実際に発言を処理するところをやっていきたい。

余談

Google App Engine SDK for Go 1.5.1にしたところ、LoggingのAPIが変わったみたいで早速コンパイルエラーが出てた。

新API

func Logger(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    c.Infof("Requested URL: %v", r.URL)
}

旧API

func Logger(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    c.Logf("Requested URL: %v", r.URL)
}

(続く)

0 件のコメント:

コメントを投稿