API DSL
goctl API
DSL是一种描述HTTP服务的简洁语言,来源于.api文件
文件结构
1 | syntax = "v1" // 必须的版本声明 |
服务块
1 | service user-api { |
API 类型
类型声明要满足如下规则:
- 以
type开头 - 不需要声明
struct关键字 - 不支持泛型和弱类型
参数
参数接收规则
可以在类型中用tag声明参数接收规则
| 接收规则 | 说明 | 生效范围 | 接收tag示例 | 请求示例 |
|---|---|---|---|---|
| json | json 序列化 | 请求体&响应体 | json:“foo” | {“key”:“value”} |
| path | 路由参数 | 请求体 | path:“id” | /foo/:id |
| form | post 请求的表单参数请求接收标识,get 请求的 query 参数接收标识 | 请求体 | form:“name” | GET /search?key=value |
| header | http 请求体接收标识 | 请求体 | header:“Content-Length” | origin: https://go-zero.dev |
其中POST支持 content-type 为 form-data 和
x-www-form-urlencoded
注意:gozero中不支持多tag接收参数,即一个字段只能有一个tag
参数校验规则
还可以在tag中添加参数校验,只对请求体有效,支持的规则如下:
| 接收规则 | 说明 | 示例 |
|---|---|---|
| optional | 当前字段是可选参数,允许为零值(zero value) | json:“foo,optional” |
| options | 当前参数仅可接收的枚举值 | json:“gender,options=foo|bar” |
| default | 当前参数默认值 | json:“gender,default=male” |
| range | 当前参数数值有效范围,仅对数值有效 | json:“age,range=[0:120]” |
注意,range表达式用小括号表示开区间,中括号表示闭区间,当min值缺省时,min代表0,max值缺省时,mnax代表无穷,两者不能同时缺省
路由规则
API描述语言中,路由需要满足如下规则:
- 以
/开头 - 路由节点以
/分隔 - 路由节点中可以包含
:,但:必须是路由节点的第一个字符,它后面的节点值必须在请求体中有pathtag声明,用于接收路由参数 - 路由节点可以包含数字、字母、下划线和中划线
1 | service Demo { |
路由分组
随着业务扩展,服务接口也会变多,生成代码文件也会变多,我们需要将生成的代码文件按照维度聚合,便于开发和维护
没有分组的情况,生成的代码handler和logic下的目录都是混在一起的,不方便管理和阅读
API语言中,我们可以通过在@server语句块中使用group关键字分组
1 | @server ( |
这样可以将不同的业务逻辑分到不同的目录下
路由前缀
假设我们有一个用户服务,需要通过路由来区分不同版本,可以通过api语言声明路由前缀
1 | https://example.com/v1/users |
可以通过在@server语句块中使用prefix关键字声明路由前缀:
1 | @server ( |
生成的代码会通过rest.WithPrfix来声明路由前缀
中间件声明
API语言中,在@server语句块中使用middleware关键字声明中间件的,多个中间件以英文逗号分割,这样生成的代码会单独开一个middleware文件夹
1 | @server( |
中间件代码由goctl自动生成,是一个结构体,存在一个Handle方法,接收一个http.HandlerFunc参数并返回一个http.HandlerFunc参数,用于对请求处理并传递给下一个中间件或handler
例:我们在中间件中将header的User-Agent信息存到context中
1 | type UserAgentMiddleware struct { |
context的传递:
- 在一个新中间件中间件中,使用
r.Context()获取传入请求的context - 使用
context.WithValue()新建带值的ctx - 使用
r.WithContext()得到新的request,传到下游 - 下游代码可以通过
r.Context()获取值
JWT鉴权
JWT是一种开放标准,用于在网络应用间传递声明式信息,是一种基于JSON的轻量级身份验证和授权机制,用于在客户端和服务端之间安全传输信息
在API文件中,在@server语句块中使用jwt:Auth开启jwt认证,它仅对下面对应的路由有效,使用Auth作为jwt的值,经过goctl代码生成后会转成对应的jwt配置:
1 | package config |
route.go中生成了通过rest.WithJwt来声明jwt认证
注意:对于jwt token的生成和refresh token仍需开发者自行实现
jwt通常也可以携带一些自定义信息,比如server端生成jwt
key时添加了custom-key值,go-zero解析后会将所有载体放到context中,获取载体信息示例如下:
1 | func (l *UserInfoLogic) UserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) { |
请求签名
gozero中内置了签名功能,可以通过api语言开启,并生成签名代码
在@server代码块中使用signature关键字开启签名功能
1 | @server ( |
生成的路由代码会自动携带rest.WithSignature
API导入
随着业务量增大,API文件也会越来越大,写在同一个api文件中api文件也会变得巨大,不易阅读和维护,所以可以通过api import引入其他api文件
假设HTTP服务响应统一为如下json格式:
1 | { |
前两个是固定的,data可变,所以我们可以将前两个字段抽象出来,定义一个公共结构体,在其他api文件中引入该结构体,例如抽象在bash.api中:
1 | syntax = "v1" |
这样其他api文件可以通过import "base.api"引入
由于api描述语言中没有package的概念,所以引入时需要用相对路径
SSE路由
SSE是服务器推送事件,一种服务器向浏览器单向推送实时数据的轻量协议
工作原理
- 客户端发起一个普通HTTP请求,且header中告诉服务器自己支持SSE
- 服务器保持该连接不关闭,并持续推送文本消息(以
text/event-stream格式) - 每条消息都是一个事件,浏览器通过js的
EventSource对象自动接收,不用反复轮询
SSE支持
gozero中可以通过在@server语句块中开启sse:true注解来支持SSE
指定开启后,goctl会带有rest.WithSSE()选项的路由,该选项会自动:
- 设置适当的SSE头部
- 移除写入超时以允许长时间运行的连接
- 启用连接保持活动
注意:
- SSE路由通常应该使用GET方法
- 连接将保持打开,知道客户端断开连接或处理程序完成
- SSE可与JWT认证和中间件良好配合使用