Go實戰 21 | 網路程式設計:玩轉 RESTful API 服務

Swenson1992發表於2021-03-19

什麼是 RESTful API

RESTful API 是一套規範,它可以規範如何對伺服器上的資源進行操作。在瞭解 RESTful API 之前,先介紹下 HTTP Method,因為 RESTful API 和它是密不可分的。

說起 HTTP Method,最常見的就是POST和GET,其實最早在 HTTP 0.9 版本中,只有一個GET方法,該方法是一個冪等方法,用於獲取伺服器上的資源,也就是在瀏覽器中直接輸入網址回車請求的方法。

在 HTTP 1.0 版本中又增加了HEAD和POST方法,其中常用的是 POST 方法,一般用於給服務端提交一個資源,導致伺服器的資源發生變化。

隨著網路越來越複雜,發現這兩個方法是不夠用的,就繼續新增了方法。所以在 HTTP1.1 版本的時候,一口氣增加到了 9 個,新增的方法有 HEAD、OPTIONS、PUT、DELETE、TRACE、PATCH 和 CONNECT。下面一一介紹它們的作用。

  • GET 方法可請求一個指定資源的表示形式,使用 GET 的請求應該只被用於獲取資料。
  • HEAD 方法用於請求一個與 GET 請求的響應相同的響應,但沒有響應體。
  • POST 方法用於將實體提交到指定的資源,通常導致伺服器上的狀態變化或副作用。
  • PUT 方法用於請求有效載荷替換目標資源的所有當前表示。
  • DELETE 方法用於刪除指定的資源。
  • CONNECT 方法用於建立一個到由目標資源標識的伺服器的隧道。
  • OPTIONS 方法用於描述目標資源的通訊選項。
  • TRACE 方法用於沿著到目標資源的路徑執行一個訊息環回測試。
  • PATCH 方法用於對資源應用部分修改。

從以上每個方法的介紹可以看到,HTTP 規範針對每個方法都給出了明確的定義,所以使用的時候也要儘可能地遵循這些定義,這樣在開發中才可以更好地協作。

理解了這些 HTTP 方法,就可以更好地理解 RESTful API 規範了,因為 RESTful API 規範就是基於這些 HTTP 方法規範我們對伺服器資源的操作,同時規範了 URL 的樣式和 HTTP Status Code。

在 RESTful API 中,使用的主要是以下五種 HTTP 方法:

  • GET,表示讀取伺服器上的資源;
  • POST,表示在伺服器上建立資源;
  • PUT,表示更新或者替換伺服器上的資源;
  • DELETE,表示刪除伺服器上的資源;
  • PATCH,表示更新 / 修改資源的一部分。

以上 HTTP 方法在 RESTful API 規範中是一個操作,操作的就是伺服器的資源,伺服器的資源通過特定的 URL 表示。

現在通過一些示例讓你更好地理解 RESTful API,如下所示:

HTTP GET https://www.flysnow.org/users
HTTP GET https://www.flysnow.org/users/123

以上是兩個 GET 方法的示例:

  • 第一個表示獲取所有使用者的資訊;
  • 第二個表示獲取 ID 為 123 使用者的資訊。

下面再看一個 POST 方法的示例,如下所示:

HTTP POST https://www.flysnow.org/users

這個示例表示建立一個使用者,通過 POST 方法給伺服器提供建立這個使用者所需的全部資訊。

注意:這裡 users 是個複數。

現在已經知道了如何建立一個使用者,那麼如果要更新某個特定的使用者怎麼做呢?其實也非常簡單,示例程式碼如下所示:

HTTP PUT https://www.flysnow.org/users/123

這表示要更新 / 替換 ID 為 123 的這個使用者,在更新的時候,會通過 PUT 方法提供更新這個使用者需要的全部使用者資訊。這裡 PUT 方法和 POST 方法不太一樣的是,從 URL 上看,PUT 方法操作的是單個資源,比如這裡 ID 為 123 的使用者。

小提示:如果要更新一個使用者的部分資訊,使用 PATCH 方法更恰當。

看到這裡,已經知道了如何刪除一個使用者,示例程式碼如下所示:

HTTP DELETE https://www.flysnow.org/users/123

DELETE 方法的使用和 PUT 方法一樣,也是操作單個資源,這裡是刪除 ID 為 123 的這個使用者。

一個簡單的 RESTful API

現在開始,通過一個使用 Golang 實現 RESTful API 風格的示例,加深 RESTful API 的理解。

Go 語言的一個很大的優勢,就是可以很容易地開發出網路後臺服務,而且效能快、效率高。在開發後端 HTTP 網路應用服務的時候,需要處理很多 HTTP 的請求訪問,比如常見的RESTful API 服務,就要處理很多 HTTP 請求,然後把處理的資訊返回給使用者。對於這類需求,Golang 提供了內建的 net/http 包處理這些 HTTP 請求,可以比較方便地開發一個 HTTP 服務。

下面來看一個簡單的 HTTP 服務的 Go 語言實現,程式碼如下所示:

func main() {
   http.HandleFunc("/users",handleUsers)
   http.ListenAndServe(":8080", nil)
}
func handleUsers(w http.ResponseWriter, r *http.Request){
   fmt.Fprintln(w,"ID:1,Name:張三")
   fmt.Fprintln(w,"ID:2,Name:李四")
   fmt.Fprintln(w,"ID:3,Name:王五")
}

這個示例執行後,在瀏覽器中輸入 localhost:8080/users, 就可以看到如下內容資訊:

ID:1,Name:張三
ID:2,Name:李四
ID:3,Name:王五

也就是獲取所有的使用者資訊,但是這並不是一個 RESTful API,因為使用者不僅可以通過 HTTP GET 方法獲得所有的使用者資訊,還可以通過 POST、DELETE、PUT 等 HTTP 方法獲得所有的使用者資訊,這顯然不符合 RESTful API 的規範。

現在對以上示例進行修改,使它符合 RESTful API 的規範,修改後的示例程式碼如下所示:

func handleUsers(w http.ResponseWriter, r *http.Request){
   switch r.Method {
   case "GET":
      w.WriteHeader(http.StatusOK)
      fmt.Fprintln(w,"ID:1,Name:張三")
      fmt.Fprintln(w,"ID:2,Name:李四")
      fmt.Fprintln(w,"ID:3,Name:王五")
   default:
      w.WriteHeader(http.StatusNotFound)
      fmt.Fprintln(w,"not found")
   }
}

這裡只修改了 handleUsers 函式,在該函式中增加了只在使用 GET 方法時,才獲得所有使用者的資訊,其他情況返回 not found。

現在再執行這個示例,會發現只能通過 HTTP GET 方法進行訪問了,使用其他方法會提示 not found。

RESTful JSON API

在專案中最常見的是使用 JSON 格式傳輸資訊,也就是提供的 RESTful API 要返回 JSON 內容給使用者。

同樣用上面的示例,把它改造成可以返回 JSON 內容的方式,示例程式碼如下所示:

//資料來源,類似MySQL中的資料
var users = []User{
   {ID: 1,Name: "張三"},
   {ID: 2,Name: "李四"},
   {ID: 3,Name: "王五"},
}
func handleUsers(w http.ResponseWriter, r *http.Request){
   switch r.Method {
   case "GET":
      users,err:=json.Marshal(users)
      if err!=nil {
         w.WriteHeader(http.StatusInternalServerError)
         fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")
      }else {
         w.WriteHeader(http.StatusOK)
         w.Write(users)
      }
   default:
      w.WriteHeader(http.StatusNotFound)
      fmt.Fprint(w,"{\"message\": \"not found\"}")
   }
}
//使用者
type User struct {
   ID int
   Name string
}

從以上程式碼可以看到,這次的改造主要是新建了一個 User 結構體,並且使用 users 這個切片儲存所有的使用者,然後在 handleUsers 函式中把它轉化為一個 JSON 陣列返回。這樣,就實現了基於 JSON 資料格式的 RESTful API。

執行這個示例,在瀏覽器中輸入 http://localhost:8080/users,可以看到如下資訊:

[{“ID”:1,”Name”:”張三”},{“ID”:2,”Name”:”李四”},{“ID”:3,”Name”:”王五”}]

這已經是 JSON 格式的使用者資訊,包含了所有使用者。

Gin 框架

雖然 Go 語言自帶的 net/http 包,可以比較容易地建立 HTTP 服務,但是它也有很多不足:

  • 不能單獨地對請求方法(POST、GET 等)註冊特定的處理函式;
  • 不支援 Path 變數引數;
  • 不能自動對 Path 進行校準;
  • 效能一般;
  • 擴充套件性不足;
  • ……

基於以上這些不足,出現了很多 Golang Web 框架,如 Mux,Gin、Fiber 等,今天介紹的就是這款使用最多的 Gin 框架。

引入 Gin 框架

Gin 框架是一個在 Github 上開源的 Web 框架,封裝了很多 Web 開發需要的通用功能,並且效能也非常高,可以很容易地寫出 RESTful API。

Gin 框架其實是一個模組,也就是 Go Mod,所以採用 Go Mod 的方法引入即可。

首先需要下載安裝 Gin 框架,安裝程式碼如下:

$ go get -u github.com/gin-gonic/gin

然後就可以在 Go 語言程式碼中匯入使用了,匯入程式碼如下:

import "github.com/gin-gonic/gin"

通過以上安裝和匯入這兩個步驟,就可以在專案中使用 Gin 框架了。

使用 Gin 框架

現在,已經引入了 Gin 框架,下面用 Gin 框架重寫上面的示例,修改的程式碼如下所示:

func main() {
   r:=gin.Default()
   r.GET("/users", listUser)
   r.Run(":8080")
}
func listUser(c *gin.Context)  {
   c.JSON(200,users)
}

相比 net/http 包,Gin 框架的程式碼非常簡單,通過它的 GET 方法就可以建立一個只處理 HTTP GET 方法的服務,而且輸出 JSON 格式的資料也非常簡單,使用 c.JSON 方法即可。

最後通過 Run 方法啟動 HTTP 服務,監聽在 8080 埠。現在執行這個 Gin 示例,在瀏覽器中輸入 http://localhost:8080/users,看到的資訊和通過 net/http 包實現的效果是一樣的。

獲取特定的使用者

現在已經掌握瞭如何使用 Gin 框架建立一個簡單的 RESTful API,並且可以返回所有的使用者資訊,那麼如何獲取特定使用者的資訊呢?

如果要獲得特定使用者的資訊,需要使用的是 GET 方法,並且 URL 格式如下所示:

http://localhost:8080/users/2

以上示例中的 2 是使用者的 ID,也就是通過 ID 來獲取特定的使用者。

下面通過 Gin 框架 Path 路徑引數來實現這個功能,示例程式碼如下:

func main() {
   //省略沒有改動的程式碼
   r.GET("/users/:id", getUser)
}
func getUser(c *gin.Context) {
   id := c.Param("id")
   var user User
   found := false
   //類似於資料庫的SQL查詢
   for _, u := range users {
      if strings.EqualFold(id, strconv.Itoa(u.ID)) {
         user = u
         found = true
         break
      }
   }
   if found {
      c.JSON(200, user)
   } else {
      c.JSON(404, gin.H{
         "message": "使用者不存在",
      })
   }
}

在 Gin 框架中,路徑中使用冒號表示 Path 路徑引數,比如示例中的 :id,然後在 getUser 函式中可以通過 c.Param(“id”) 獲取需要查詢使用者的 ID 值。

小提示:Param 方法的引數要和 Path 路徑引數中的一致,比如示例中都是 ID。

現在執行這個示例,通過瀏覽器訪問 http://localhost:8080/users/2,就可以獲得 ID 為 2 的使用者,輸出資訊如下所示:

{"ID":2,"Name":"李四"}

可以看到,已經正確的獲取到了 ID 為 2 的使用者,他的名字叫李四。

假如訪問一個不存在的 ID,會得到什麼結果呢?比如 99,示例如下所示:

➜ curl http://localhost:8080/users/99
{"message":"使用者不存在"}

從以上示例輸出可以看到,返回了『使用者不存在』的資訊,和程式碼中處理的邏輯一樣。

新增一個使用者

現在已經可以使用 Gin 獲取所有使用者,還可以獲取特定的使用者,那麼應該知道如何新增一個使用者了,現在通過 Gin 實現如何新增一個使用者,看和你想的方案是否相似。

根據 RESTful API 規範,實現新增使用的是 POST 方法,並且 URL 的格式為 localhost:8080/users ,向這個 URL 傳送資料,就可以新增一個使用者,然後返回建立的使用者資訊。

現在使用 Gin 框架實現新增一個使用者,示例程式碼如下:

func main() {
   //省略沒有改動的程式碼
   r.POST("/users", createUser)
}
func createUser(c *gin.Context) {
   name := c.DefaultPostForm("name", "")
   if name != "" {
      u := User{ID: len(users) + 1, Name: name}
      users = append(users, u)
      c.JSON(http.StatusCreated,u)
   } else {
      c.JSON(http.StatusOK, gin.H{
         "message": "請輸入使用者名稱稱",
      })
   }
}

以上新增使用者的主要邏輯是獲取客戶端上傳的 name 值,然後生成一個 User 使用者,最後把它儲存到 users 集合中,達到新增使用者的目的。

在這個示例中,使用 POST 方法來新增使用者,所以只能通過 POST 方法才能新增使用者成功。

現在執行這個示例,然後通過如下命令傳送一個新增使用者的請求,檢視結果:

➜ curl -X POST -d 'name=Golang' http://localhost:8080/users
{"ID":4,"Name":"Golang"}

可以看到新增使用者成功,並且返回了新增的使用者,還有分配的 ID。

總結

Go 語言已經提供了比較強大的 SDK,可以很容易地開發網路服務的應用,而藉助第三方的 Web 框架,可以讓這件事情更容易、更高效。比如這篇文章介紹的 Gin 框架,就可以很容易開發出 RESTful API,更多關於 Gin 框架的使用可以參考 Golang Gin 實戰系列文章。

在做專案開發的時候,要善於藉助已經有的輪子,讓自己的開發更有效率,也更容易實現。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
golang

相關文章