REST APIを作るための記事を名乗っていながら、前回書いた記事では全然RESTっぽいことやってなかったことに気付いた。ということでAPI部分から変えてしきりなおし!
コメント取得用のAPI
RESTは(GETに関して言えば)極力URLで表現しようよ、というスタンスなので、前回のようにパラメータで欲しいコメントを取得するのは実はあまりREST的でない。なので、APIもよりREST的な形に変更する。
http://localhost:8080/1/remark/get/[コメントのID]
前回と違って、get以降に取得したいコメントのIDをそのままURLで指定するようにした。
サーバーサイド
APIの変更にあわせてサーバーサイドのコードも少し変える。前回からの変更点は以下の通り。
- ".../get/"以降に直接コメントのURLが指定されてたら指定されたコメントだけを返す。
- ".../get/"までしか指定されてなかったらコメントを全て返す。
当然、存在しないコメントのIDを指定された場合の適切なエラー処理も必要。ということで以下がそのコード。
func init() {
http.HandleFunc("/", errorHandler(root))
http.HandleFunc("/1/remark/post/", errorHandler(postRemark))
http.HandleFunc("/1/remark/get/", errorHandler(getRemark))
}
...
func getRemarkID(path string) (int64, bool) {
split := strings.Split(path, "/", -1)
length := len(split)
for i := 0; i<len(split); i++ {
if split[i] == "get" &&
i+1 < length { // next word may not exist
id, err := strconv.Atoi64(split[i+1])
return id, (err == nil)
}
}
return 0,false
}
// 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("Requested URL: %s\n", r.RawURL)
c.Infof("Requested URL path: %s\n", r.URL.Path)
// Check if we were requested for a specific remark
id, foundID := getRemarkID(r.URL.Path)
var query *datastore.Query
if foundID {
// If we were, create a query to get that remark
c.Infof("Retrieving specified remark id: %d\n", id)
query = datastore.NewQuery(REMARK_KIND).Filter("Time=", datastore.Time(id))
} else {
// If we weren't, create a query to get all the remarks (new remarks come first)
c.Infof("Retrieving all remarks\n")
query = datastore.NewQuery(REMARK_KIND).Order("-Time")
}
// check if any results exist
count, err := query.Count(c)
if err != nil {
http.Error(w, err.String(), http.StatusInternalServerError)
return
}
if count == 0 {
msg := "Specified remark does not exist\n"
c.Infof(msg)
http.Error(w, msg, http.StatusNotFound)
return
}
// We have a result.
// query for it.
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)
return
}
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)
}
}
...
前回よりコードが長くなっている。注意するのは、http.HandleFunc("/1/remark/get/", errorHandler(getRemark))のようにハンドラを指定するとき、URLの末尾にはしっかりスラッシュをつけるということ。こうすることで、getから先にまだURLが続いていても同じハンドラ関数が呼ばれるようになる。このスラッシュをちゃんと指定しないと、"/1/remark/get/123151..."のようなURLへリクエストを出したときはgetRemarkハンドラが呼ばれなくなってしまう。
クライアントサイド
クライアント側はコメントのIDをURLに追加するくらいで、それほど変更点は無い。
package main
import (
"http"
"fmt"
"os"
)
func main() {
client := new(http.Client)
remarkUrlRoot := "http://localhost:8080/1/remark/"
getUrl := remarkUrlRoot + "get/1311387270000000"
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()
}
サーバーサイドの出力
2011/07/23 12:17:09 INFO: Requested URL: /1/remark/get/1311387270000000 2011/07/23 12:17:09 INFO: Requested URL path: /1/remark/get/1311387270000000 2011/07/23 12:17:09 INFO: Retrieving specified remark id: 1311387270000000 2011/07/23 12:17:09 INFO: Retrieved id:1311387270 remark:aardvark INFO 2011-07-23 12:17:09,169 dev_appserver.py:4248] "GET /1/remark/get/1311387270000000 HTTP/1.1" 200 -
クライアントサイドの出力
200 OK id=1311387270000000&remark=aardvark Finished reading
次回
ということで改めてGET用のAPIを作ったところで、次回はDELETEあたりをやることにする。
(続く)
0 件のコメント:
コメントを投稿