亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

詳解Go語言RESTful JSON API創建

2020-04-01 18:57:13
字體:
來源:轉載
供稿:網友

RESTful API在Web項目開發中廣泛使用,本文針對Go語言如何一步步實現RESTful JSON API進行講解, 另外也會涉及到RESTful設計方面的話題。

也許我們之前有使用過各種各樣的API, 當我們遇到設計很糟糕的API的時候,簡直感覺崩潰至極。希望通過本文之后,能對設計良好的RESTful API有一個初步認識。

JSON API是什么?

JSON之前,很多網站都通過XML進行數據交換。如果在使用過XML之后,再接觸JSON, 毫無疑問,你會覺得世界多么美好。這里不深入JSON API的介紹,有興趣可以參考jsonapi。

基本的Web服務器

從根本上講,RESTful服務首先是Web服務。 因此我們可以先看看Go語言中基本的Web服務器是如何實現的。下面例子實現了一個簡單的Web服務器,對于任何請求,服務器都響應請求的URL回去。

package mainimport (  "fmt"  "html"  "log"  "net/http")func main() {  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))  })  log.Fatal(http.ListenAndServe(":8080", nil))}

上面基本的web服務器使用Go標準庫的兩個基本函數HandleFunc和ListenAndServe。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {  DefaultServeMux.HandleFunc(pattern, handler)}func ListenAndServe(addr string, handler Handler) error {  server := &Server{Addr: addr, Handler: handler}  return server.ListenAndServe()}

運行上面的基本web服務,就可以直接通過瀏覽器訪問http://localhost:8080來訪問。

> go run basic_server.go

添加路由

雖然標準庫包含有router, 但是我發現很多人對它的工作原理感覺很困惑。 我在自己的項目中使用過各種不同的第三方router庫。 最值得一提的是Gorilla Web ToolKit的mux router。

另外一個流行的router是來自Julien Schmidt的叫做httprouter的包。

package mainimport (  "fmt"  "html"  "log"  "net/http"  "github.com/gorilla/mux")func main() {  router := mux.NewRouter().StrictSlash(true)  router.HandleFunc("/", Index)  log.Fatal(http.ListenAndServe(":8080", router))}func Index(w http.ResponseWriter, r *http.Request) {  fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))}

要運行上面的代碼,首先使用go get獲取mux router的源代碼:

> go get github.com/gorilla/mux

上面代碼創建了一個基本的路由器,給請求"/"賦予Index處理器,當客戶端請求http://localhost:8080/的時候,就會執行Index處理器。

如果你足夠細心,你會發現之前的基本web服務訪問http://localhost:8080/abc能正常響應: 'Hello, "/abc"', 但是在添加了路由之后,就只能訪問http://localhost:8080了。 原因很簡單,因為我們只添加了對"/"的解析,其他的路由都是無效路由,因此都是404。

創建一些基本的路由

既然我們加入了路由,那么我們就可以再添加更多路由進來了。

假設我們要創建一個基本的ToDo應用, 于是我們的代碼就變成下面這樣:

package mainimport (  "fmt"  "log"  "net/http"  "github.com/gorilla/mux")func main() {  router := mux.NewRouter().StrictSlash(true)  router.HandleFunc("/", Index)  router.HandleFunc("/todos", TodoIndex)  router.HandleFunc("/todos/{todoId}", TodoShow)  log.Fatal(http.ListenAndServe(":8080", router))}func Index(w http.ResponseWriter, r *http.Request) {  fmt.Fprintln(w, "Welcome!")}func TodoIndex(w http.ResponseWriter, r *http.Request) {  fmt.Fprintln(w, "Todo Index!")}func TodoShow(w http.ResponseWriter, r *http.Request) {  vars := mux.Vars(r)  todoId := vars["todoId"]  fmt.Fprintln(w, "Todo Show:", todoId)}

在這里我們添加了另外兩個路由: todos和todos/{todoId}。

這就是RESTful API設計的開始。

請注意最后一個路由我們給路由后面添加了一個變量叫做todoId。

這樣就允許我們傳遞id給路由,并且能使用具體的記錄來響應請求。

基本模型

路由現在已經就緒,是時候創建Model了,可以用model發送和檢索數據。在Go語言中,model可以使用結構體來實現,而其他語言中model一般都是使用類來實現。

package mainimport (  "time")type Todo struct {  Name   string  Completed bool  Due    time.Time}type Todos []Todo

上面我們定義了一個Todo結構體,用于表示待做項。 另外我們還定義了一種類型Todos, 它表示待做列表,是一個數組,或者說是一個分片。

稍后你就會看到這樣會變得非常有用。

返回一些JSON

我們有了基本的模型,那么我們可以模擬一些真實的響應了。我們可以為TodoIndex模擬一些靜態的數據列表。

package mainimport (  "encoding/json"  "fmt"  "log"  "net/http"  "github.com/gorilla/mux")// ...func TodoIndex(w http.ResponseWriter, r *http.Request) {  todos := Todos{    Todo{Name: "Write presentation"},    Todo{Name: "Host meetup"},  }  json.NewEncoder(w).Encode(todos)}// ...

現在我們創建了一個靜態的Todos分片來響應客戶端請求。注意,如果你請求http://localhost:8080/todos, 就會得到下面的響應:

[  {    "Name": "Write presentation",    "Completed": false,    "Due": "0001-01-01T00:00:00Z"  },  {    "Name": "Host meetup",    "Completed": false,    "Due": "0001-01-01T00:00:00Z"  }]

更好的Model

對于經驗豐富的老兵來說,你可能已經發現了一個問題。響應JSON的每個key都是首字母答寫的,雖然看起來微不足道,但是響應JSON的key首字母大寫不是習慣的做法。 那么下面教你如何解決這個問題:

type Todo struct {  Name   string  `json:"name"`  Completed bool   `json:"completed"`  Due    time.Time `json:"due"`}

其實很簡單,就是在結構體中添加標簽屬性, 這樣可以完全控制結構體如何編排(marshalled)成JSON。

拆分代碼

到目前為止,我們所有代碼都在一個文件中。顯得雜亂, 是時候拆分代碼了。我們可以將代碼按照功能拆分成下面多個文件。

我們準備創建下面的文件,然后將相應代碼移到具體的代碼文件中:

  1. main.go: 程序入口文件。
  2. handlers.go: 路由相關的處理器。
  3. routes.go: 路由。
  4. todo.go: todo相關的代碼。
package mainimport (  "encoding/json"  "fmt"  "net/http"  "github.com/gorilla/mux")func Index(w http.ResponseWriter, r *http.Request) {  fmt.Fprintln(w, "Welcome!")}func TodoIndex(w http.ResponseWriter, r *http.Request) {  todos := Todos{    Todo{Name: "Write presentation"},    Todo{Name: "Host meetup"},  }  if err := json.NewEncoder(w).Encode(todos); err != nil {    panic(err)  }}func TodoShow(w http.ResponseWriter, r *http.Request) {  vars := mux.Vars(r)  todoId := vars["todoId"]  fmt.Fprintln(w, "Todo show:", todoId)}
package mainimport (  "net/http"  "github.com/gorilla/mux")type Route struct {  Name    string  Method   string  Pattern   string  HandlerFunc http.HandlerFunc}type Routes []Routefunc NewRouter() *mux.Router {  router := mux.NewRouter().StrictSlash(true)  for _, route := range routes {    router.      Methods(route.Method).      Path(route.Pattern).      Name(route.Name).      Handler(route.HandlerFunc)  }  return router}var routes = Routes{  Route{    "Index",    "GET",    "/",    Index,  },  Route{    "TodoIndex",    "GET",    "/todos",    TodoIndex,  },  Route{    "TodoShow",    "GET",    "/todos/{todoId}",    TodoShow,  },}
package mainimport "time"type Todo struct {  Name   string  `json:"name"`  Completed bool   `json:"completed"`  Due    time.Time `json:"due"`}type Todos []Todo
package mainimport (  "log"  "net/http")func main() {  router := NewRouter()  log.Fatal(http.ListenAndServe(":8080", router))}

更好的Routing

我們重構的過程中,我們創建了一個更多功能的routes文件。 這個新文件利用了一個包含多個關于路由信息的結構體。 注意,這里我們可以指定請求的類型,例如GET, POST, DELETE等等。

輸出Web日志

在拆分的路由文件中,我也包含有一個不可告人的動機。稍后你就會看到,拆分之后很容易使用另外的函數來修飾http處理器。

首先我們需要有對web請求打日志的能力,就像很多流行web服務器那樣的。 在Go語言中,標準庫里邊沒有web日志包或功能, 因此我們需要自己創建。

package loggerimport (  "log"  "net/http"  "time")func Logger(inner http.Handler, name string) http.Handler {  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    start := time.Now()    inner.ServeHTTP(w, r)    log.Printf(      "%s/t%s/t%s/t%s",      r.Method,      r.RequestURI,      name,      time.Since(start),    )  })}

上面我們定義了一個Logger函數,可以給handler進行包裝修飾。

這是Go語言中非常標準的慣用方式。其實也是函數式編程的慣用方式。 非常有效,我們只需要將Handler傳入該函數, 然后它會將傳入的handler包裝一下,添加web日志和耗時統計功能。

應用Logger修飾器

要應用Logger修飾符, 我們可以創建router, 我們只需要簡單的將我們所有的當前路由都包到其中, NewRouter函數修改如下:

func NewRouter() *mux.Router {  router := mux.NewRouter().StrictSlash(true)  for _, route := range routes {    var handler http.Handler    handler = route.HandlerFunc    handler = Logger(handler, route.Name)    router.      Methods(route.Method).      Path(route.Pattern).      Name(route.Name).      Handler(handler)  }  return router}

現在再次運行我們的程序,我們就可以看到日志大概如下:

2014/11/19 12:41:39 GET /todos TodoIndex 148.324us

這個路由文件太瘋狂...讓我們重構它吧

路由routes文件現在已經變得稍微大了些, 下面我們將它分解成多個文件:

  1. routes.go
  2. router.go
package mainimport "net/http"type Route struct {  Name    string  Method   string  Pattern   string  HandlerFunc http.HandlerFunc}type Routes []Routevar routes = Routes{  Route{    "Index",    "GET",    "/",    Index,  },  Route{    "TodoIndex",    "GET",    "/todos",    TodoIndex,  },  Route{    "TodoShow",    "GET",    "/todos/{todoId}",    TodoShow,  },}
package mainimport (  "net/http"  "github.com/gorilla/mux")func NewRouter() *mux.Router {  router := mux.NewRouter().StrictSlash(true)  for _, route := range routes {    var handler http.Handler    handler = route.HandlerFunc    handler = Logger(handler, route.Name)    router.      Methods(route.Method).      Path(route.Pattern).      Name(route.Name).      Handler(handler)  }  return router}

另外再承擔一些責任

到目前為止,我們已經有了一些相當好的樣板代碼(boilerplate), 是時候重新審視我們的處理器了。我們需要稍微多的責任。 首先修改TodoIndex,添加下面兩行代碼:

func TodoIndex(w http.ResponseWriter, r *http.Request) {  todos := Todos{    Todo{Name: "Write presentation"},    Todo{Name: "Host meetup"},  }  w.Header().Set("Content-Type", "application/json; charset=UTF-8")  w.WriteHeader(http.StatusOK)  if err := json.NewEncoder(w).Encode(todos); err != nil {    panic(err)  }}

這里發生了兩件事。 首先,我們設置了響應類型并告訴客戶端期望接受JSON。第二,我們明確的設置了響應狀態碼。

Go語言的net/http服務器會嘗試為我們猜測輸出內容類型(然而并不是每次都準確的), 但是既然我們已經確切的知道響應類型,我們總是應該自己設置它。

稍等片刻,我們的數據庫在哪里?

很明顯,如果我們要創建RESTful API, 我們需要一些用于存儲和檢索數據的地方。然而,這個是不是本文的范圍之內, 因此我們將簡單的創建一個非常簡陋的模擬數據庫(非線程安全的)。

我們創建一個repo.go文件,內容如下:

package mainimport "fmt"var currentId intvar todos Todos// Give us some seed datafunc init() {  RepoCreateTodo(Todo{Name: "Write presentation"})  RepoCreateTodo(Todo{Name: "Host meetup"})}func RepoFindTodo(id int) Todo {  for _, t := range todos {    if t.Id == id {      return t    }  }  // return empty Todo if not found  return Todo{}}func RepoCreateTodo(t Todo) Todo {  currentId += 1  t.Id = currentId  todos = append(todos, t)  return t}func RepoDestroyTodo(id int) error {  for i, t := range todos {    if t.Id == id {      todos = append(todos[:i], todos[i+1:]...)      return nil    }  }  return fmt.Errorf("Could not find Todo with id of %d to delete", id)}

給Todo添加ID

我們創建了模擬數據庫,我們使用并賦予id, 因此我們相應的也需要更新我們的Todo結構體。

package mainimport "time"type Todo struct {  Id    int    `json:"id"`  Name   string  `json:"name"`  Completed bool   `json:"completed"`  Due    time.Time `json:"due"`}type Todos []Todo

更新我們的TodoIndex

要使用數據庫,我們需要在TodoIndex中檢索數據。修改代碼如下:

func TodoIndex(w http.ResponseWriter, r *http.Request) {  w.Header().Set("Content-Type", "application/json; charset=UTF-8")  w.WriteHeader(http.StatusOK)  if err := json.NewEncoder(w).Encode(todos); err != nil {    panic(err)  }}

POST JSON

到目前為止,我們只是輸出JSON, 現在是時候進入存儲一些JSON了。

在routes.go文件中添加如下路由:

Route{  "TodoCreate",  "POST",  "/todos",  TodoCreate,},

Create路由

func TodoCreate(w http.ResponseWriter, r *http.Request) {  var todo Todo  body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))  if err != nil {    panic(err)  }  if err := r.Body.Close(); err != nil {    panic(err)  }  if err := json.Unmarshal(body, &todo); err != nil {    w.Header().Set("Content-Type", "application/json; charset=UTF-8")    w.WriteHeader(422) // unprocessable entity    if err := json.NewEncoder(w).Encode(err); err != nil {      panic(err)    }  }  t := RepoCreateTodo(todo)  w.Header().Set("Content-Type", "application/json; charset=UTF-8")  w.WriteHeader(http.StatusCreated)  if err := json.NewEncoder(w).Encode(t); err != nil {    panic(err)  }}

首先我們打開請求的body。 注意我們使用io.LimitReader。這樣是保護服務器免受惡意攻擊的好方法。假設如果有人想要給你服務器發送500GB的JSON怎么辦?

我們讀取body以后,我們解構Todo結構體。 如果失敗,我們作出正確的響應,使用恰當的響應碼422, 但是我們依然使用json響應回去。 這樣可以允許客戶端理解有錯發生了, 而且有辦法知道到底發生了什么錯誤。

最后,如果所有都通過了,我們就響應201狀態碼,表示請求創建的實體已經成功創建了。 我們同樣還是響應回代表我們創建的實體的json, 它會包含一個id, 客戶端可能接下來需要用到它。

POST一些JSON

我們現在有了偽repo, 也有了create路由,那么我們需要post一些數據。 我們使用curl通過下面的命令來達到這個目的:

 

復制代碼代碼如下:
curl -H "Content-Type: application/json" -d '{"name": "New Todo"}' http://localhost:8080/todos

 

如果你再次通過http://localhost:8080/todos訪問,大概會得到下面的響應:

[  {    "id": 1,    "name": "Write presentation",    "completed": false,    "due": "0001-01-01T00:00:00Z"  },  {    "id": 2,    "name": "Host meetup",    "completed": false,    "due": "0001-01-01T00:00:00Z"  },  {    "id": 3,    "name": "New Todo",    "completed": false,    "due": "0001-01-01T00:00:00Z"  }]

我們還沒有做的事情

雖然我們已經有了很好的開端,但是還有很多事情沒有做:

  1. 版本控制: 如果我們需要修改API, 結果完全改變了怎么辦? 可能我們需要在我們的路由開頭加上/v1/prefix?
  2. 授權: 除非這些都是公開/免費API, 我們可能還需要授權。 建議學習JSON web tokens的東西。

eTag - 如果你正在構建一些需要擴展的東西,你可能需要實現eTag。

還有什么?

對于所有項目來說,開始都很小,但是很快就變得失控了。但是如果我們想要將它帶到另外一個層次, 讓他生產就緒, 還有一些額外的事情需要做:

  1. 大量重構(refactoring).
  2. 為這些文件創建幾個包,例如一些JSON助手、修飾符、處理器等等。
  3. 測試, 使得,你不能忘記這點。這里我們沒有做任何測試。對于生產系統來說,測試是必須的。

源代碼:https://github.com/corylanou/tns-restful-json-api

總結

對我來說,最重要的,需要記住的是我們要建立一個負責任的API。 發送適當的狀態碼,header等,這些是API廣泛采用的關鍵。我希望本文能讓你盡快開始自己的API。

參考鏈接

Go語言RESTful JSON API實現
JSON API
Gorilla Web Toolkit
httprouter
JSON Web Tokens
eTag

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲精品第一页| 色婷婷综合久久久久| 国产欧美精品一区二区三区介绍| 亚洲一二三在线| 亚洲精品xxxx| 亚洲高清福利视频| 亚洲丁香久久久| 日韩中文字幕网站| 日韩视频中文字幕| 亚洲欧洲视频在线| 中文字幕日韩高清| 亚洲最大av网站| 欧美日韩中文字幕在线| 日韩电影免费观看在线观看| 久久精品2019中文字幕| 国产日韩精品视频| 亚洲男人天堂九九视频| 久久91精品国产| 精品国产欧美一区二区三区成人| 日韩精品丝袜在线| 亚洲一区av在线播放| 日韩在线激情视频| 国产精品看片资源| 久久天天躁狠狠躁夜夜爽蜜月| 国产精品久久久久久久久粉嫩av| 91在线国产电影| 最近2019好看的中文字幕免费| 中文字幕在线观看亚洲| 岛国av在线不卡| 国产一区二区三区精品久久久| 久久精品国亚洲| 日韩网站在线观看| 久久久久成人精品| 国产区精品在线观看| 日本高清视频一区| 美女久久久久久久久久久| 日韩精品免费综合视频在线播放| 亚洲精品综合久久中文字幕| 成人亚洲综合色就1024| 大桥未久av一区二区三区| 亚洲性线免费观看视频成熟| 韩曰欧美视频免费观看| 91免费看片在线| 日韩欧美精品网址| 久久影院在线观看| 国产精品视频午夜| 欧美大全免费观看电视剧大泉洋| 三级精品视频久久久久| 欧美亚洲激情视频| 国产在线观看精品一区二区三区| 91久久国产婷婷一区二区| 国产精品美女网站| 91国产精品电影| 91精品视频在线播放| 亚洲2020天天堂在线观看| 久久久久久久久久久久久久久久久久av| 91精品久久久久| 欧美日韩福利在线观看| 91av在线播放| 国产大片精品免费永久看nba| 国产精品69久久久久| 久久黄色av网站| 91精品国产自产91精品| 亚洲国产精品久久久久秋霞蜜臀| 欧美性猛交xxxx免费看漫画| 色综合色综合久久综合频道88| 亚洲国产婷婷香蕉久久久久久| 国产精品va在线| 久久精品99无色码中文字幕| 久久夜色精品国产欧美乱| 亚洲欧洲一区二区三区久久| 国产亚洲精品综合一区91| 国产91精品不卡视频| 91深夜福利视频| 最近更新的2019中文字幕| 精品久久久一区二区| 91国产美女在线观看| 欧美情侣性视频| 亚洲黄色av女优在线观看| 久久精品国产久精国产思思| 久久精品久久久久久国产 免费| 亚洲欧美日韩中文在线制服| 亚洲免费av电影| 午夜精品久久久久久久久久久久久| 九色精品免费永久在线| 中国人与牲禽动交精品| 国产精品678| 欧美一区二区三区艳史| 91精品国产成人| 国产欧美精品一区二区三区-老狼| 国产精品久久久久久搜索| 国内精品久久久| 欧美日韩国产成人高清视频| 中文字幕九色91在线| 亚洲а∨天堂久久精品9966| 中文字幕日本欧美| 国产精品一区二区久久久| 性欧美亚洲xxxx乳在线观看| 中文字幕日韩精品有码视频| 欧美激情国内偷拍| 综合激情国产一区| 亚洲精品欧美日韩| 国产精品露脸自拍| 国产一区二区三区在线观看网站| www.美女亚洲精品| 亚洲成人999| 韩国一区二区电影| 色悠悠久久久久| 亚洲国产第一页| 中文字幕一精品亚洲无线一区| 国产欧美日韩丝袜精品一区| 国产精品69精品一区二区三区| 日本91av在线播放| 国产精品免费一区豆花| 国产裸体写真av一区二区| 性日韩欧美在线视频| 久久久精品亚洲| 亚洲色图偷窥自拍| 麻豆乱码国产一区二区三区| 亚洲精品久久久久久久久久久久久| 精品国产91久久久| 亚洲欧美中文日韩在线v日本| 亚洲级视频在线观看免费1级| 国产午夜精品全部视频播放| 欧美精品一区二区三区国产精品| 免费不卡在线观看av| 欧美亚洲视频一区二区| 美女福利视频一区| 在线播放国产一区中文字幕剧情欧美| 欧美日韩国内自拍| 中日韩午夜理伦电影免费| 中文字幕亚洲国产| 欧美激情啊啊啊| 亚洲国产三级网| 久久久久久com| 亚洲最大在线视频| 97精品一区二区三区| 国产成人啪精品视频免费网| 国产精品私拍pans大尺度在线| 欧日韩不卡在线视频| 欧美大胆a视频| 性日韩欧美在线视频| 亚洲国产精品久久久久| 懂色av一区二区三区| 亚洲久久久久久久久久| 亚洲一区二区久久久| 青青草原成人在线视频| 美日韩精品视频免费看| 成人动漫网站在线观看| 精品久久久久久| 亚洲xxxx3d| 成人福利在线视频| 国产www精品| 青草青草久热精品视频在线观看| 亚洲va电影大全| 国产午夜精品理论片a级探花| 亚洲国产成人在线播放| 成人黄色av网站| 精品美女永久免费视频| 精品国偷自产在线视频99| 欧美亚洲一级片| 日韩大片免费观看视频播放| 欧美日韩激情美女| 97在线视频免费看|