0ce0f47ac126f75191ad6026faf6af03
Go RESTful API

一、前言

上一篇 我们讲解了 web 编程的文件上传。本篇主要介绍下 web 编程中的 RESTful API。通过本篇你将了解到什么是 RESTful API、Go 操作数据库、Go ORM 等知识。

二、RESTful API

RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计。
要理解 RESTful ,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。

Representational

Representational 代表"表现层"。"表现层"其实指的是"资源"(Resources)的"表现层"
所谓"资源",就是网络上的一个实体。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。你可以用一个 URI 指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符

Resources 可以有多种外在表现形式Resources 具体呈现出来的形式,就是它的"表现层"(Representation)。
比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。
URI只代表资源的实体,不代表它的形式。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。

State Transfer

从客户端发起请求到服务端返回消息,这是个互动的过程。在这个过程中,就涉及到数据和状态的变化
HTTP 是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,就是HTTP协议里面,五个表示操作方式的动词:GET、POST、PUT、PATCH、DELETE。它们分别对应五种基本操作:

  • GET 用来获取资源
  • POST 用来新建资源
  • PUT 用来更新资源
  • PATCH 用来更新资源,通常是部分更新
  • DELETE 用来删除资源

三、API 设计

RESTful 的核心思想就是,客户端发出的数据操作指令都是动宾结构。比如,GET /users 这个命令,GET是动词,/users 是宾语。

宾语

宾语就是要获取的资源。所以,宾语必须是名词,不能是动词。比如,/users 这个 URL 就是正确的,而下面的 URL 不是名词,所以都是错误的。

/getUsers
/createNewUser
/deleteUsers

复数

宾语必须是名词,那么应该使用复数,还是单数?
这没有统一的规定,但是常见的操作是读取一个集合,比如GET /users,这里明显应该是复数
为了统一,建议都使用复数,比如 GET /users/18 要好于GET /user/18

多级 URL

资源经常需要多级分类,因此很容易写出多级的 URL,比如获取某个用户的某一个爱好。

GET /users/1/hobbys/1

这种 URL 不利于扩展,语义也不明确,往往要想一会,才能明白含义。
更好的做法是,除了第一级,其他级别都用查询字符串表达

GET /users/12?categories=1

再举一个例子,查询是 VIP 的用户。

GET /users/isVip

查询字符串的写法明显更好。

GET /users?isVip=true

状态码

状态码已在 Go web 初探说明,这里不再赘述。

服务器回应

不要返回纯本文

API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的 Content-Type 属性要设为 application/json
客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的 ACCEPT 属性也要设成 application/json

GET /orders/2 HTTP/1.1 
Accept: application/json

发生错误时,不要返回 200 状态码

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

上面的例子中,需等到解析数据体以后,才能得知操作失败
这实际上取消了状态码的作用,完全不可取的。正确的做法是,状态码反映发生的错误具体的错误信息放在数据体里面返回。

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}

提供链接

API 的使用者未必知道,URL 的设计。所以,在回应中给出相关链接,就便于使用者下一步操作。用户只要记住一个 URL,就可以发现其他的 URL。这种方法叫做 HATEOAS
举例来说,GitHub 的 API 都在 api.github.com 这个域名。访问它,就可以得到其他 URL。

{
  ...
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  ...
}

上面的回应中,挑一个 URL 访问,又可以得到别的 URL。对于用户来说,不需要记住 URL 设计,只要从 api.github.com 一步步查找就可以了。
HATEOAS 的格式没有统一规定,上面例子中,GitHub 将它们与其他属性放在一起。更好的做法应该是,将相关链接与其他属性分开

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "In progress",
   "links": {[
    { "rel":"cancel", "method": "delete", "href":"/api/status/12345" } ,
    { "rel":"edit", "method": "put", "href":"/api/status/12345" }
  ]}
}

因为 RESTful API 常常跟数据库的增、删、改、查离不开。所以,下面我们先来安装下 mysql 数据库,便于后面的实践。

四、mysql

前往 官网下载 mysql 安装包。建议也下载下 workbench 方便操作数据库。

安装

设置 root 密码

使用 workbench 连接上数据库,并创建 test 库用于测试

创建 user 测试表

数据库的表的命名很有讲究,详见 https://www.cnblogs.com/slgkaifa/p/7245519.html

五、RESTful API 实例

创建项目,并添加 gin 和 gorm 依赖

dep ensure -add github.com/gin-gonic/gin
dep ensure -add github.com/jinzhu/gorm

添加路由

```
package main

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"

)

func main() {
fmt.Println("GO API")

InitialMigration()
handleRequests()

}

func handleRequests() {
r := gin.Default()

r.GET("/", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello World!")
})

r.GET("/users", AllUsers)

r.GET("/user/:name", FindUser)

r.POST("/user", NewUser)

r.DELETE("/user/:name", DeleteUser)

r.PUT("/user/:name/:age/:password", UpdateUser)
top Created with Sketch.