[toc]
0x00 前言简述 描述: 在当前云原生以及微服务流行的环境下,越来越多的开发者使用API接口实现数据的增删改查(CURD),将应用间的依赖解耦合,提高代码复用,便于水平扩展。所以为SZJX 😳 作为一名运开有必要进行相关规范的学习实践。
Q: 什么是API?
API (Application Programming Interface ,应用程序编程接口) 是一些预先定义的函数或者接口,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无须访问源码,或理解内部工作机制的细节。 简单的说,通过API请求接口我们可以实现任意数据操作,并且可以更加直观简约描述该请求的操作,更便于代码复用。
Q: API 服务器的设计规范和组合
我们要实现一个API 接口服务器时,需要考虑两个方面一个是API设计风格,另外一个是返回的数据类型。 此处以Go语言为例,在 API 开发中常见的组合是 REST + JSON
或者 gRPC + Protobuf
,其中 REST
和 gRPC
即是API设计风格,而 JSON
和 Protobuf
则是请求返回的数据类型,当然我们也可以使用返回XML格式,上述组合是开发中常用(推荐),在实际开发中还需根据业务需要以及环境来选型。
此处,由于作者实际学习需要,本章主要介绍 RESTful API 设计规范,在后续文章中也会归纳总结 gRPC 设计规范。
0x01 RESTful API 接口简介 描述: RESTful API 是目前比较成熟的一套互联网应用程序的API设计理念,REST (REpresentational State Transfer
)表现层状态转移,由 Roy Fielding 在他的论文中提出。
REST 是一种软件架构风格不是技术框架, 实际上是一组架构约束条件和原则,当满足REST有一系列规范的 API 均可称为 RESTful API
RESTful 架构具有结构清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多开发者喜爱并广泛应用在各类的网站上!
RESTful API 核心规范
REST 中一切实体都被抽象成资源,每个资源有一个唯一的标识 URI ,所有的行为都应该是在资源上的 CRUD 操作
REST 中是无状态的,即请求都包含了所有足够完成本次操作的依赖信息,服务器端无须依赖Session(会话保持
),PS: 无状态对于服务端的弹性扩容是很重要的。
REST 中天生和 HTTP 协议相辅相成,所以使用标准的HTTP协议方法 POST 、 DELETE 、 PUT 、 GET 方法来对应 REST 资源的增、删、改、查操作。
HTTP方法
行为操作
API URL
操作说明
GET
获取资源列表
/users
获取用户账号列表
GET
获取一个具体的资源
/users/admin
获取admin账号的相关详细信息
POST
创建一个新的资源
/users/weiyigeek
创建一个weiyigeek的用户账号
PUT
以整体的方式更新一个资源
/users/10000
更新用户ID为10000的账号
DELETE
删除服务器上的一个资源
/users/10000
删除id为10000的账号
0x02 RESTful API 接口设计 描述: 前面快速描述 RESTful API 接口规范,本节将实践根据规范来进行 RESTful API 设计,以及在实际开发中应该怎么做。
域名规范 描述: 应该尽量将API部署在专用域名之下,如果确定API很简单不会有进一步扩展,可以考虑放在主域名下。
[toc]
0x00 前言简述 描述: 在当前云原生以及微服务流行的环境下,越来越多的开发者使用API接口实现数据的增删改查(CURD),将应用间的依赖解耦合,提高代码复用,便于水平扩展。所以为SZJX 😳 作为一名运开有必要进行相关规范的学习实践。
Q: 什么是API?
API (Application Programming Interface ,应用程序编程接口) 是一些预先定义的函数或者接口,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无须访问源码,或理解内部工作机制的细节。 简单的说,通过API请求接口我们可以实现任意数据操作,并且可以更加直观简约描述该请求的操作,更便于代码复用。
Q: API 服务器的设计规范和组合
我们要实现一个API 接口服务器时,需要考虑两个方面一个是API设计风格,另外一个是返回的数据类型。 此处以Go语言为例,在 API 开发中常见的组合是 REST + JSON
或者 gRPC + Protobuf
,其中 REST
和 gRPC
即是API设计风格,而 JSON
和 Protobuf
则是请求返回的数据类型,当然我们也可以使用返回XML格式,上述组合是开发中常用(推荐),在实际开发中还需根据业务需要以及环境来选型。
此处,由于作者实际学习需要,本章主要介绍 RESTful API 设计规范,在后续文章中也会归纳总结 gRPC 设计规范。
0x01 RESTful API 接口简介 描述: RESTful API 是目前比较成熟的一套互联网应用程序的API设计理念,REST (REpresentational State Transfer
)表现层状态转移,由 Roy Fielding 在他的论文中提出。
REST 是一种软件架构风格不是技术框架, 实际上是一组架构约束条件和原则,当满足REST有一系列规范的 API 均可称为 RESTful API
RESTful 架构具有结构清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多开发者喜爱并广泛应用在各类的网站上!
RESTful API 核心规范
REST 中一切实体都被抽象成资源,每个资源有一个唯一的标识 URI ,所有的行为都应该是在资源上的 CRUD 操作
REST 中是无状态的,即请求都包含了所有足够完成本次操作的依赖信息,服务器端无须依赖Session(会话保持
),PS: 无状态对于服务端的弹性扩容是很重要的。
REST 中天生和 HTTP 协议相辅相成,所以使用标准的HTTP协议方法 POST 、 DELETE 、 PUT 、 GET 方法来对应 REST 资源的增、删、改、查操作。
HTTP方法
行为操作
API URL
操作说明
GET
获取资源列表
/users
获取用户账号列表
GET
获取一个具体的资源
/users/admin
获取admin账号的相关详细信息
POST
创建一个新的资源
/users/weiyigeek
创建一个weiyigeek的用户账号
PUT
以整体的方式更新一个资源
/users/10000
更新用户ID为10000的账号
DELETE
删除服务器上的一个资源
/users/10000
删除id为10000的账号
0x02 RESTful API 接口设计 描述: 前面快速描述 RESTful API 接口规范,本节将实践根据规范来进行 RESTful API 设计,以及在实际开发中应该怎么做。
域名规范 描述: 应该尽量将API部署在专用域名之下,如果确定API很简单不会有进一步扩展,可以考虑放在主域名下。
1 2 3 4 5 https://api.weiyigeek.top https://weiyigeek.top/api/
版本规范 描述: 应尽量使用版本( Versioning )和 应用的应用程序名称来规范。
1 2 3 4 5 6 7 8 9 http://api.weiyigeek.top/app/1.0/foo http://api.weiyigeek.top/app/1.1/foo Accept: app.weiyigeek-top.foo+json; version=1.0 Accept: app.weiyigeek-top.foo+json; version=2.0
路径规范 描述 : 路径又称 “ 终点 “ ( endpoint ),表示 API 的具体网址,每个网址代表一种资源(resource)
使用名词
对于一个简洁结构,你应该始终用名词, 资源作为网址,只能有名词不能有动词 ,而且所用的名词往往与数据库的表名对应
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 GET /products GET /products/4 PATCH(或)PUT /products/4 POST /products /getProducts /listOrders /retreiveClientByOrder?orderId=1
使用复数
API 中的名词应该使用复数,无论子资源或者所有资源。
1 2 3 4 GET http://api.weiyigeek.top/AppName/v1/products/1 GET http://api.weiyigeek.top/AppName/v1/products
请求操作规范 描述: 前面我们在讲解RESTful API 核心规范中说到对于资源的具体操作类型通常与HTTP请求方法对应,其中用的HTTP动词如下所示,其中括号里是对应的 SQL 命令。
HTTP动词 1 2 3 4 5 6 7 GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 DELETE(DELETE):从服务器删除资源。 PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。 HEAD:获取资源的元数据。 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 GET http://api.weiyigeek.top/AppName/v1/products POST http://api.weiyigeek.top/AppName/v1/products PUT http://api.weiyigeek.top/AppName/v1/products/1 DELETE http://api.weiyigeek.top/AppName/v1/products/1 POST http://api.weiyigeek.top/AppName/v1/products/1/update POST http://api.weiyigeek.top/AppName/v1/products/1/delete
过滤信息规范 描述: 如果API返回的数量很多,服务器不可能都将它们返回给用户,此时 API 应该提供参数,过滤返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 GET http://api.weiyigeek.top/AppName/v1/products?limit =10 GET http://api.weiyigeek.top/AppName/v1/products?offset=10 GET http://api.weiyigeek.top/AppName/v1/products?page=2&per_page=100 GET http://api.weiyigeek.top/AppName/v1/products?sortby=name&order=asc GET http://api.weiyigeek.top/AppName/v1/products?animal_type_id=1 比如,GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
状态码规范 描述: 服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。
状态码
适用请求
备注
200 OK
[GET]
服务器成功返回用户请求的数据
201 CREATED
[POST/PUT/PATCH]
用户新建或修改数据成功。
202 Accepted
[*]
表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT
[DELETE]
用户删除数据成功。
400 INVALID REQUEST
[POST/PUT/PATCH]
用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized
[*]
表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden
[*]
表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND
[*]
用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable
[GET]
用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone
[GET]
用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity
[POST/PUT/PATCH]
当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR
[*]
服务器发生错误,用户将无法判断发出的请求是否成功。
返回与错误处理规范 描述: 在服务器返回的数据格式,应该尽量使用 JSON 避免使用 XML,在实际开发中常常也是使用 JSON。
标注标准返回结果,实际开发通常返回是200,错误是 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // # 正常返回 { "code" : 200 , "message" : "服务器成功返回用户请求的数据." , "data" : { "name" : "WeiyiGeek" , "blog" : "blog.weiyigeek.top" } } // # 异常返回 { "code" : 0 , "message" : "服务器未能成功返回用户请求的数据." , "data" : {} }
1 2 3 4 5 { "code" : 10002 , "message" : "User data operation completed." , "data" : {} }
定制标准化的错误返回结果 (例如,code 大于等于200)
1 2 3 4 { "code" : 20002 , "error" : "The server failed to successfully return the data requested by the user." }
0x03 RESTful API 服务器运行流程 描述: 此处以Go语言的Web框架Gin模块包为例设计的API服务器,其启动运行流程大致如下(开发自定义请根据实际情况进行设计):
1.命令启动API服务器后,其首先加载配置文件,根据配置做后面的处理工作。
2.通常会将日志相关的配置记录在配置文件中,在解析完配置文件后加载日志包初始化函数,来初始化日志实例,供后面的程序调用。
3.然后初始化数据库实例,建立数据库连接,供后面对数据库的 CRUD 操作使用。
4.设置HTTP请求,通常包括 3 方面的注册路由
、设置 Header
、注册中间件
5.然后加载设置的路由以及健康检查预警。
5.然后调用 net/http 包的 ListenAndServe() 方法启动 HTTP 服务器(即启动绑定监听接口地址和端口)。
6.启动 HTTP 端口之前,程序会 go 一个协程,来ping HTTP 服务器的 /sd/health
接口,如果程序成功启动,ping 协程在timeout 之前会成功返回,如果程序启动失败,则 ping协程最 timeout,并终止整个程序
weiyigeek.top-API 服务器运行流程图
0x04 RESTful API 简单示例 使用内置 net/http 包简单实现 描述: Golang 提供了内置的 net/http 包,用来处理这些 HTTP 请求,可以比较方便地开发一个 HTTP 服务。
文本格式 下述代码中符合 RESTful API 的规范,当我们在 handleUsers 函数中增加 GET 、POST、PUT方法时,才获得对应用户的信息,其他情况返回 not found。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "net/http" ) func main () { http.HandleFunc("/users" ,handleUsers) http.ListenAndServe(":8080" , nil ) } func handleUsers (w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET" : w.WriteHeader(http.StatusOK) fmt.Fprintln(w,"ID: 1, Name: WeiyiGeek" ) case "POST" : w.WriteHeader(http.StatusOK) fmt.Fprintln(w,"ID: 2, Name: Geeker" ) case "PUT" : w.WriteHeader(http.StatusOK) fmt.Fprintln(w,"ID: 3, Name: Security" ) default : w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w,"not found" ) } }
此时在运行程序后,在浏览器中输入 http://localhost:8080/users , 就可以看到如下内容信息:ID: 1, Name: WeiyiGeek
, 由于浏览器默认是GET请求的,如果想实践其他请求返回的数据可使用Postman工具。
RESTful JSON API 在实践项目接口开发中,数据大多数情况下会使用 json 格式来传输呈现,所以再次对示例进行改造,使它返回 json 格式的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package mainimport ( "encoding/json" "fmt" "net/http" ) func main () { http.HandleFunc("/langs" ,handleLangs) http.ListenAndServe(":8080" , nil ) } var langs = []Lang{ {ID: 1 ,Name: "Golang" }, {ID: 2 ,Name: "Java" }, {ID: 3 ,Name: "PHP" }, {ID: 4 ,Name: "Python" }, } type Lang struct { ID int Name string } func handleLangs (w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET" : langs,err:=json.Marshal(langs) if err!=nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w,"{\"message\": \"" +err.Error()+"\"}" ) }else { w.WriteHeader(http.StatusOK) w.Write(langs) } default : w.WriteHeader(http.StatusNotFound) fmt.Fprint(w,"{\"message\": \"not found\"}" ) } }
使用三方 Gin Web框架包实现 描述: 上面我们使用的是 Go 语言自带的 net/http 包,写法比较简单,但是它也有许多不足之处:
不能单独地对请求方法(POST、GET 等)注册特定的处理函数; 不支持 Path 变量参数; 不能自动对 Path 进行校准; 性能一般,扩展性不足; …… 基于以上的不足,我们可以使用其它的 Golang Web 框架, 此处笔者推荐 Gin 。(欢迎浏览博主的gin系列学习文章,正在补充中,博客地址: https://blog.weiyigeek.top )
简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package mainimport ( "net/http" "strconv" "strings" "github.com/gin-gonic/gin" ) var langs = []Lang{ {ID: 1 , Name: "Golang" }, {ID: 2 , Name: "Java" }, {ID: 3 , Name: "PHP" }, {ID: 4 , Name: "Python" }, } type Lang struct { ID int Name string } var lang Langfunc main () { r := gin.Default() r.GET("/users/:id" , func (c *gin.Context) { id := c.Param("id" ) found := false for _, data := range langs { if strings.EqualFold(id, strconv.Itoa(data.ID)) { lang = data found = true break } } if found { c.JSON(http.StatusOK, gin.H{ "code" : 200 , "data" : lang, }) } else { c.JSON(http.StatusNotFound, gin.H{ "code" : "0" , "message" : "用户不存在" , }) } }) r.POST("/users" , func (c *gin.Context) { param_lang := c.DefaultPostForm("lang" , "" ) if param_lang != "" { lang := Lang{ID: len (langs) + 1 , Name: param_lang} langs = append (langs, lang) c.JSON(http.StatusCreated, gin.H{ "code" : 200 , "data" : lang, }) } else { c.JSON(http.StatusOK, gin.H{ "code" : 0 , "message" : "请输入用户名称" , }) } }) r.Run() }
运行脚本 1 2 3 4 5 6 $ go run .\main.go ....... [GIN-debug] GET /users/:id --> main.main.func1 (3 handlers) [GIN-debug] POST /users --> main.main.func2 (3 handlers) ....... [GIN-debug] Listening and serving HTTP on :8080
执行结果: 1 2 3 4 5 6 7 8 curl --location 'http://10.20.172.106:8080/users/1' curl --location 'http://10.20.172.106:8080/users' \ --form 'lang="C语言"'
0x05 RESTful API 企业项目结构 在 Go API 项目中通常会包括很多功能,例如 Makefile 文件(编译文件)、配置文件目录、 RESTful API 服务器的 handler 目录、 model 目录、工具类目录、 vendor 目录,以及实际处理业务逻辑函数所存放的service 目录。
上述功能代码在结构中有列出,新加功能时将代码放入对应功能的目录/文件中,可以使整个项目代码结构更加清晰,非常 有利于后期的查找和维护 。
1 2 3 4 5 mkdir -vp ./config/{tls,conf} ./docs/swagger ./handler/sd/user ./model ./pkg/{errno,version} ./router/middleware ./service ./util ./vendor mkdir .\configs\tls .\configs\conf .\docs\swagger .\handler\sd\user .\model .\pkg\errno .\pkg\version .\router .\middleware .\service .\util .\vendor
weiyigeek.top-API 项目结构图