Go Web:RESTful web service示例
RESTful架構的簡介
web服務的架構模式主要有2種:SOAP和REST。SOAP和REST都回答了同一個問題:如何訪問web服務。
SOAP風格的程式是功能驅動的,要藉助xml來傳遞資料,明確表示要做什麼動作,訪問什麼資源,但使用xml是非常繁瑣複雜的事情。
RESTful風格的Web服務是資源驅動的,通過資源(名詞)和http方法GET/POST/DELETE/PUT來實現增刪改查的邏輯,偶爾也用PATCH/HEAD方法。注意,POST不是冪等的,而PUT是冪等的,所以PUT常用來更新資源,POST常用來建立資源。
關於RESTful風格的web服務,重點體現在URI上。RESTful風格的web服務的URI不能包含動詞,而是隻包含名詞(資源)。動詞或其它相關的意思可以通過http request的header/body或者通過url的query來傳遞。
例如:
1.獲取id=1的文章:/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然後用GET方法表示獲取,即show
2.使用者1轉賬500給2:POST /accounts/1/transfer/500/to/2
,transfer是動詞,是錯誤的,應該設計成名詞,並將動詞邏輯相關的引數通過URL的query或者請求報文傳遞
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
關於RESTful的web,參見阮一峰的兩篇文章:
RESTful風格的web服務示例
本示例是描述如何操作部落格文章的web服務,對於RESTful的規範來說,功能並不完善,但對於理解RESTful來說足夠了。
兩個原始碼檔案server.go和data.go位於同個目錄下,都屬於main包,server.go是執行入口,data.go是資料操作邏輯的程式碼。
以下是server.go檔案內容,定義handler。注意處理資源的方式,這是RESTful風格的web服務核心:URI中不能包含動詞,只通過資源來完成動作邏輯。
package main import ( "encoding/json" "net/http" "path" "strconv" ) func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/posts/", handleRequest) server.ListenAndServe() } func handleRequest(w http.ResponseWriter, r *http.Request) { var err error switch r.Method { case "GET": err = handleGet(w, r) case "POST": err = handlePost(w, r) case "PUT": err = handlePut(w, r) case "DELETE": err = handleDelete(w, r) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func handleGet(w http.ResponseWriter, r *http.Request) (err error) { id, err := strconv.Atoi(path.Base(r.URL.Path)) if err != nil { return } post, err := retrieve(id) if err != nil { return } output, err := json.MarshalIndent(&post, "", "\t\t") if err != nil { return } w.Header().Set("Content-Type", "application/json") w.Write(output) return } func handlePost(w http.ResponseWriter, r *http.Request) (err error) { len := r.ContentLength body := make([]byte, len) r.Body.Read(body) var post Post json.Unmarshal(body, &post) err = post.create() if err != nil { return } w.WriteHeader(200) return } func handlePut(w http.ResponseWriter, r *http.Request) (err error) { id, err := strconv.Atoi(path.Base(r.URL.Path)) if err != nil { return } post, err := retrieve(id) if err != nil { return } len := r.ContentLength body := make([]byte, len) r.Body.Read(body) json.Unmarshal(body, &post) err = post.update() if err != nil { return } w.WriteHeader(200) return } func handleDelete(w http.ResponseWriter, r *http.Request) (err error) { id, err := strconv.Atoi(path.Base(r.URL.Path)) if err != nil { return } post, err := retrieve(id) if err != nil { return } err = post.delete() if err != nil { return } w.WriteHeader(200) return }
以下是操作資料庫的data.go檔案內容:
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type Post struct {
Id int `json:"id"`
Content string `json:"content"`
Author string `json:"author"`
}
var Db *sql.DB
func init() {
var err error
Db, err = sql.Open("mysql", "root:[email protected][email protected](192.168.100.21:3306)/blog")
if err != nil {
panic(err)
}
}
func retrieve(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRow("select id, content, author from posts where id = ?", id).Scan(&post.Id, &post.Content, &post.Author)
return
}
func (post *Post) create() (err error) {
statement := "insert into posts (content, author) values (?, ?)"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
res, err := stmt.Exec(post.Content, post.Author)
if err != nil {
return
}
lastid, err := res.LastInsertId()
if err != nil {
return
}
post.Id = int(lastid)
return
}
func (post *Post) update() (err error) {
_, err = Db.Exec("update posts set content = ?, author = ? where id = ?", post.Content, post.Author, post.Id)
return
}
func (post *Post) delete() (err error) {
_, err = Db.Exec("delete from posts where id = ?", post.Id)
return
}