惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Razeen`s Blog

Let's Encrypt 推出 Gen Y 根证书架构:揭示 Web PKI 的五大未来趋势 通过 Wi-Fi 自动备份你的 iPhone 到 Nas 管理培训感悟:从技术视角看管理之路 UPS 一拖多:保护你的 NAS 和 Linux 服务器 从影音中心到 AI 助手:我的50款 Homelab 服务清单 从SSL证书有效期将缩短到47天聊开去 部署一个自己的 Running Page 谈谈特斯拉 Model Y 用车一年的感受和费用分析 弃用 Disqus 评论,使用自建 Waline 使用 Prometheus 和 Grafana 搭建你的证书监控面板 服务器迁移记:一次磁盘换板引发的Linux分区与挂载的学习 改善信息来源, 利用 RSS 高效获取资讯 (RSShub + Reeder5 + WeWe RSS) 多种 Docker 镜像拉取解决方案与实践 App分享 | AppCleaner - Mac上的卸载神器 Azure OpenAI API 申请和使用 如何开通 OneKey 虚拟信用卡,并充值消费 如何拥有一个可长期在国内使用的国外手机号码 分享开通 ChatGPT Plus 过程 如何开通 Depay 虚拟信用卡,并充值消费 如何开通欧易Web3钱包, 交易入账 如何5分钟内1块钱注册 ChatGPT 科学上网 如何注册美区的 Apple ID (2023年/无需科学上网) NAS折腾记(11): NASTool3.0体验和降级 NAS折腾记(10): Docker版本的NASTool配置 NAS折腾记(9): NASTool与微信交互,微信发送消息远程下载电影【多图】 NAS折腾记(8):群晖安装 NASTool 实现影音半自动化【多图】 内网穿透(2):Tailscale 组网实现内网穿透,操作简单,无成本 内网穿透(1):总结了11中内网穿透的方式,总有一种适合你 NAS折腾记(7):从零开始设置(黑)群晖系统 NAS折腾记(6):黑群晖系统安装好后怎么洗白? NAS折腾记(5):群晖硬盘休眠设置与分析 NAS折腾记(4): 20分钟手把手带你完美安装 DS918+ 黑群晖7.1.1 NAS折腾记(3):NAS 装机 NAS折腾记(2):B360-ITX 双M.2 双2.5G网口 6 SATA主版 开箱 NAS折腾记(1):328元的4盘位Nas机箱开箱 Openwrt + Clash 全局科学上网 Newifi3 刷入 OpenWrt 固件 v21.02 利用 Markdown 画一些流程图、时序图、甘特图等 Homelab(8): 搭建自用 Gitlab 与 Docker 仓库 Nginx Tcp 转发保留客户端真实 IP (PROXY Protocol) Homelab (7):IPSec VPN(基于证书认证)客户端设置 Homelab (6): 基于自签发证书的 IPSec VPN 搭建 Homelab (5): DDNS 动态域名解析 Homelab (4): Linux 服务器基础环境准备 Homelab (3): 整体网络与基础硬件介绍 Go学习笔记(十)老项目迁移 go module 大型灾难记录 Github Actions 初体验之自动化部署 Hexo 博客 记一次 Nginx DNS 缓存导致转发问题 Homelab (2): 电信悦me网关修改桥接模式,路由器拨号 Homelab (1):5分钟上手黑群晖 NAS 终极 Bash 脚本指南 Typora 自动上传图片到七牛云 Ubuntu 20.04 LTS 有线网卡驱动安装 折腾 Ubuntu 20.04 LTS 开发环境 Go学习笔记(九) 计时器的生命周期[译] 利用 git hook 规范你的代码与 commit message 规范 git commit message 与自动化版本控制 超详细 vim 配置 (with MacVim) 折腾服务器(开篇) 我的第一台个人服务器 Newifi3 实现低成本家庭级科学上网 Go学习笔记(八) | 使用 os/exec 执行命令 如何用 Go 调用 Windows API Mac OS 自动根据 WI-FI 名字改变网络位置 关于 Docker 清理 MIME Types 速查表 Go学习笔记(七) | 理解OAuth 2.0并实现一个客户端 我又又又把博客迁移了 Go学习笔记(六) | 使用swaggo自动生成Restful API文档 Go学习笔记(五) | 使用代码片段(snippets)提高编码效率 书单 - 2018 IPFS 初体验,利用 IPFS 托管你的静态网站 记一次 PostgreSQL LIKE 索引优化,联合字段 LIKE 查询优化。 Disqus 添加有趣的 Reactions 的功能 TLS 1.3 详解 (RFC 8446解读) gRPC在Go中的使用(三)gRPC实现TLS加密通信与流模式 gRPC在Go中的使用(二)gRPC实现简单通讯 gRPC在Go中的使用(一)Protocol Buffers语法与相关使用 CentOS 安装 tshark 抓包工具 Go学习笔记(四) | win上使用VSCode搭建Go开发环境 日常 Postgres 数据库点滴记录 简单了解 PKCS 规范 美食篇 | DIY戚风蛋糕(烤箱做蛋糕) Go学习笔记(三) | 怎么写Go基准测试(性能测试) TLS1.3正式更新,为Nginx添加TLS1.3的支持 一次诡异的数据库删除 GitHub Pages自定义域名开启HTTPS 证书透明度是什么?它是怎么工作的? Go学习笔记(二) | 我对 recover 的一点误解 搭建证书透明度(certificate-transparency)日志服务之从入门到放弃 修复远程登陆 Centos 时,出现 UTF-8 Warning HTTPS篇之SSL握手过程详解 Go学习笔记(一) | postgres与golang点点滴滴 AWS 命令行界面(aws-cli)从安装到快速上手 数字证书分类及怎么区分各类数字证书 常用 linux 命令小结(一)文件目录操作 云服务器搭建 hexo 博客,git hooks自动更新 SSH 免密登陆, SSH Config 配置 Golang CGO Mac 交叉编译 Windows 使用 goose 让数据库迁移更加轻松
Golang 中的 RESTful API 最佳实践
2020-03-15 · via Razeen`s Blog

RESRful API已经流行很多年了,我也一直在使用它。最佳实践也看过不少,但当一个项目完成,再次回顾/梳理项目时,会发现很多API和规范还是多少有些出入。在这篇文章中,我们结合Go Web再次梳理一下RESTful API的相关最佳实践。

示例完整代码在这里

关于RESTful API

关于什么是RESTful API,不再累述。推荐几个相关链接。

1.使用JSON

不管是接收还是返回数据都推荐使用JSON。

通常返回数据的格式有JSON和XML,但XML过于冗长,可读性差,而且各种语言的解析上也不如JSON,使用JSON的好处,显而易见。

而接收数据,我们这里也推荐使用JSON,对于后端开发而言,入参直接与模型绑定,省去冗长的参数解析能简化不少代码,而且JSON能更简单的传递一些更复杂的结构等。

正如示例代码中的这一段,我们以gin框架为例。

// HandleLogin doc
func HandleLogin(c *gin.Context) {
	param := &LoginParams{}
	if err := c.BindJSON(param); err != nil {
		c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"})
		return
	}

	// 做一些校验
	// ...

	session := sessions.Default(c)
	session.Set(sessionsKey, param.UserID)
	session.Save()
	c.JSON(http.StatusOK, &Resp{Data: "login succeed"})
}

通过c.BindJSON,轻松的将入参于模型LoginParams绑定;通过c.JSON轻松的将数据JSON序列化返回。

但所有接口都必须用JSON么?那也未必。比如文件上传,这时我们使用FormData比把文件base64之类的放到JSON里面更高效。

2.路径中不包含动词

我们的HTTP请求方法中已经有GET,POST等这些动作了,完全没有必要再路径中加上动词。

我们常用HTTP请求方法包括GET,POST,PUTDELETE, 这也对应了我们经常需要做的数据库操作。GET查找/获取资源,POST新增资源,PUT修改资源,DELETE删除资源。

如下,这些路径中没有任何动词,简洁明了。

// 获取文章列表
v1.GET("/articles", HandleGetArticles)
// 发布文章
v1.POST("/articles", HandlePostArticles)
// 修改文章
v1.PUT("/articles", HandleUpdateArticles)
// 删除文章
v1.DELETE("/articles/:id", HandleDeleteArticles)

3.路径中对应资源用复数

就像我们上面这段代码,articles对于的是我们的文章资源,背后就是一张数据库表articles, 所以操作这个资源的应该都用复数形式。

4.次要资源可分层展示

一个博客系统中,最主要的应该是文章了,而评论应该是其子资源,我们可以评论嵌套在它的父资源后面,如:

// 获取评论列表
v1.GET("/articles/:articles_id/comments", HandleGetComments)
// 添加评论
v1.POST("/articles/:articles_id/comments", HandleAddComments)
// 修改评论
v1.PUT("/articles/:articles_id/comments/:id", HandleUpdateComments)
// 删除评论
v1.DELETE("/articles/:articles_id/comments/:id", HandleDeleteComments)

那么,我们需要获取所有文章的评论怎么办?可以这么写:

v1.GET("/articles/-/comments", HandleGetComments)

但这也不是决对的,资源虽然有层级关系,但这种层级关系不宜太深,个人感觉两层最多了,如果超过,可以直接拿出来放在一级。

5.分页、排序、过滤

获取列表时,会使用到分页、排序过滤。一般:

?page=1&page_size=10  # 指定页面page与分页大小page_size
?sort=-create_at,+author # 按照创建时间create_at降序,作者author升序排序
?title=helloworld # 按字段title搜索

6.统一数据格式

不管是路径的格式,还是参数的格式,还是返回值的格式建议统一形式。

一般常用的格式有蛇形,大驼峰小驼峰,个人比较喜欢蛇形。Anyway, 不管哪种,只要统一即可。

除了参数的命名统一外,返回的数据格式,最好统一,方便前端对接。

如下,我们定义Resp为通用返回数据结构,Data中存放反会的数据,如果出错,将错误信息放在Error中。

// Resp doc
type Resp struct {
	Data  interface{} `json:"data"`
	Error string      `json:"error"`
}

// 登陆成功返回
c.JSON(http.StatusOK, &Resp{Data: "login succeed"})

// 查询列表
c.JSON(http.StatusOK, &Resp{Data: map[string]interface{}{
	"result": tempStorage,
	"total":  len(tempStorage),
}})

// 参数错误
c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"})

7.善用HTTP状态码

HTTP状态码有很多,我们没有必要也不可能全部用上,常用如下:

  • 200 StatusOK - 只有成功请求都返回200。
  • 400 StatusBadRequest - 当出现参数不对,用户参数校验不通过时,给出该状态,并返回Error
  • 401 StatusUnauthorized - 没有登陆/经过认证
  • 403 Forbidden - 服务端拒绝授权(如密码错误),不允许访问
  • 404 Not Found - 路径不存在
  • 500 Internal Server Error - 所请求的服务器遇到意外的情况并阻止其执行请求
  • 502 Bad Gateway - 网关或代理从上游接收到了无效的响应
  • 503 Service Unavailable - 服务器尚未处于可以接受请求的状态

其中502,503,我们写程序时并不会明确去抛出。所以我们平常用6个状态码已经能很好的展示服务端状态了。

同时,我们将状态与返回值对应起来,200状态下,返回Data数据;其他状态返回Error

8.API版本化

正如Demo中所示,我们将路由分组到了/api/v1路径下面,版本化API。如果后续的服务端升级,但可能仍有很大部分客户端请求未升级,依然请求老版本的API,那么我们只需要增加/api/v2,然后在该路径下为已升级的客户端提供服务。这样,我们就做到了API的版本控制,可以平滑的从一个版本切换到另外一个版本。

	v1 := r.Group("/api/v1")
	{
		v1.POST("/login", HandleLogin)
		v1.GET("/articles", HandleGetArticles)
		v1.GET("/articles/:id/comments", HandleGetComments)
    // ....

9. 统一 ‘/‘ 开头

所以路由中,路径都以’/‘开头,虽然框架会为我们做这件事,但还是建议统一加上。

10. 增加/更新操作 返回资源

对于POST,PUT操作,建议操作后,返回更新后的资源。

11. 使用HTTPS

对于暴露出去的接口/OpenAPI,一定使用HTTPS。一般时候,我们可以直接在服务前面架设一个WebServer,在WebServer内部署证书即可。当然,如果是直接由后端暴露出的接口,有必要直接在后端开启HTTPS!

12. 规范的API文档

对于我们这种前后端分离的架构,API文档是很重要。在Go中,我们很容易的能用swag结合代码注释自动生成API文档,在 <使用swaggo自动生成Restful API文档>中,我详细的介绍了怎么生成以及怎么写注释。

总结

API写的好不好,重要的还是看是否遵循WEB标准和保持一致性,最终目的也是让这些API更清晰,易懂,安全,希望这些建议对你有所帮助。