REST API

面向开发者的 REST API 及示例。

REST API

术语 REST API 通常指通过 HTTP 协议、按预定义的一组面向资源的 URL 访问的 API。 REST API 以 API 的 JSON 包装接口形式实现。借助它可以创建、读取、更新和删除资源,并调用任意控制台命令。

要开始使用 REST API,请在 IP Service 菜单中启用 www-ssl 或 www 服务。

当 www-ssl 服务运行时(HTTPS 访问),可以连接到:

  • https://<router_IP>/rest。

当 www 服务运行时(HTTP 访问),可以连接到:

  • http://<router_IP>/rest。

使用 HTTP 访问(www 服务)的主要风险是认证凭据可能被被动窃听者读取。仅在攻击者无法访问的网络上进行测试,或确信数据无法被解密时(例如 HTTP 通过加密隧道传输)才使用它。

最简单的方式是使用 cURL、wget、fetch 工具或任何其他简单的 HTTP 客户端。

$ curl -k -u admin: https://10.155.101.214/rest/system/resource

[{"architecture-name":"tile","board-name":"CCR1016-12S-1S+",
"build-time":"2020-12-04 14:19:51","cpu":"tilegx","cpu-count":"16",
"cpu-frequency":"1200","cpu-load":"1","free-hdd-space":"83439616",
"free-memory":"1503133696","platform":"MikroTik",
"total-hdd-space":"134217728","total-memory":"2046820352",
"uptime":"2d20h12m20s","version":"7.1beta4 (development)"}]

认证

REST API 的认证使用 HTTP Basic Auth。用户名和密码与控制台用户相同(默认是 admin,无密码)。

要使用安全连接(HTTPS),请在 Certificate 菜单 中设置证书,并配置 www-ssl 使用新建或导入的证书。如果使用自签名证书,请将其 CA 导入客户端的受信任根证书。测试时可以非安全方式连接(cURL 使用 -k,wget 使用 --no-check-certificate)。

JSON 格式

服务器大体遵循 ECMA-404 标准,有以下说明:

  • 在 JSON 回复中,所有对象值都编码为字符串,即使底层数据是数字或布尔值。
  • 服务器也接受八进制格式(以 0 开头)和十六进制格式(以 0x 开头)的数字。如果数字以字符串格式发送,则假定为十进制格式。
  • 不支持带指数的数字。

HTTP 方法

下表汇总了支持的 HTTP 方法

HTTP VerbCRUDROS描述
GETReadprint获取记录。
PATCHUpdate/Modifyset更新单条记录。
PUTCreateadd创建新记录。
DELETEDeleteremove删除单条记录。
POST用于访问所有控制台命令的通用方法。

GET

此方法允许从 URL 中指定的菜单获取所有记录或单条记录的列表。 例如,获取所有 IP 地址(等价于 ip/address/print CLI 命令):

$ curl -k -u admin: https://10.155.101.214/rest/ip/address

[{".id":"*1","actual-interface":"ether2","address":"10.0.0.111/24","disabled":"false",
"dynamic":"false","interface":"ether2","invalid":"false","network":"10.0.0.0"},
{".id":"*2","actual-interface":"ether3","address":"10.0.0.109/24","disabled":"true",
"dynamic":"false","interface":"ether3","invalid":"false","network":"10.0.0.0"}]

在 URL 末尾附加 ID 可返回单条记录:

$ curl -k -u admin: https://10.155.101.214/rest/ip/address/*1

{".id":"*1","actual-interface":"ether2","address":"10.0.0.111/24","disabled":"false",
"dynamic":"false","interface":"ether2","invalid":"false","network":"10.0.0.0"}

如果表中包含命名参数,可以使用名称代替 ID;例如获取 ether1:

curl -k -u admin: https://10.155.101.214/rest/interface/ether1

也可以过滤输出;例如只返回属于 10.155.101.0 网络的动态地址:

$ curl -k -u admin: "https://10.155.101.214/rest/ip/address?network=10.155.101.0&dynamic=true"

[{".id":"*8","actual-interface":"sfp12","address":"10.155.101.214/24","disabled":"false",
"dynamic":"true","interface":"sfp12","invalid":"false","network":"10.155.101.0"}]

另一个示例只返回 "dummy" 接口上且注释为 "test" 的地址:

$ curl -k -u admin: 'https://10.155.101.214/rest/ip/address?comment=test&interface=dummy'

[{".id":"*3","actual-interface":"dummy","address":"192.168.99.2/24","comment":"test",
"disabled":"false","dynamic":"false","interface":"dummy","invalid":"false","network":"192.168.99.0"}]

如果只希望返回特定属性,可以使用 .proplist,后接 = 字符和以逗号分隔的属性列表。例如只显示 address 以及是否 disabled:

$ curl -k -u admin: https://10.155.101.214/rest/ip/address?.proplist=address,disabled

[{"address":"10.0.0.111/24","disabled":"false"},{"address":"10.0.0.109/24","disabled":"true"}]

PATCH

使用此方法更新单条记录。将 PATCH 请求的 body 设置为一个 JSON 对象,其中包含字段及其新值。例如添加一条注释:

$ curl -k -u admin: -X PATCH https://10.155.101.214/rest/ip/address/*3 \
  --data '{"comment": "test"}' -H "content-type: application/json"

{".id":"*3","actual-interface":"dummy","address":"192.168.99.2/24","comment":"test",
"disabled":"false","dynamic":"false","interface":"dummy","invalid":"false","network":"192.168.99.0"}

更新成功时,服务器会返回包含其全部参数的更新后对象。

PUT

此方法创建新记录。将请求 body 设置为一个 JSON 对象,其中包含该记录的参数。每个请求只能创建一条记录。

成功后,服务器会返回包含其全部参数的已创建对象。 例如,向 dummy 接口添加一个 IP 地址:

$ curl -k -u admin: -X PUT https://10.155.101.214/rest/ip/address \
  --data '{"address": "192.168.111.111", "interface": "dummy"}' -H "content-type: application/json"

{".id":"*A","actual-interface":"dummy","address":"192.168.111.111/32","disabled":"false",
"dynamic":"false","interface":"dummy","invalid":"false","network":"192.168.111.111"}

DELETE

使用此方法删除指定 ID 的记录。如果删除成功,服务器会返回空 body。例如,连续调用两次相同的删除,第二次尝试时路由器会返回 404 错误:

$ curl -k -u admin: -X DELETE https://10.155.101.214/rest/ip/address/*9
$ curl -k -u admin: -X DELETE https://10.155.101.214/rest/ip/address/*9
{"error":404,"message":"Not Found"}

POST

所有 API 功能都可以通过 POST 使用。将命令词编码到 URL 中,并将可选参数作为 JSON 对象的字段传递。例如,要更改当前用户的密码,发送:

POST https://router/rest/password
{"old-password":"old","new-password":"N3w", "confirm-new-password":"N3w"}

REST 响应与 API 响应具有相同的结构:

  • 如果响应包含 !re 语句(记录),JSON 回复将包含对象列表。
  • 如果 !done 语句包含数据,JSON 回复将包含带有该数据的对象。
  • 如果 !done 语句中没有记录或数据,响应将为空列表。

有两个特殊键:.proplist 和 .query,它们与 print 命令词一起使用。关于 API 响应、属性列表和查询的更多内容,请参阅 API 文档。

Proplist

使用 .proplist 键指定要返回的属性。其值可以是单个逗号分隔的字符串:

POST https://router/rest/interface/print
{".proplist":"name,type"}

也可以是字符串列表:

POST https://router/rest/interface/print
{".proplist":["name","type"]}

例如,从 ip/address 菜单返回 address 和 interface 属性:

$ curl -k -u admin: -X POST https://10.155.101.214/rest/ip/address/print\
  --data '{".proplist": ["address","interface"]}' -H "content-type: application/json"

[{"address":"192.168.99.2/24","interface":"dummy"},
{"address":"172.16.5.1/24","interface":"sfpplus1"},
{"address":"172.16.6.1/24","interface":"sfp2"},
{"address":"172.16.7.1/24","interface":"sfp3"},
{"address":"10.155.101.214/24","interface":"sfp12"},
{"address":"192.168.111.111/32","interface":"dummy"}]

Query

.query 键用于创建查询栈。其值是查询词的列表。例如:

POST https://router/rest/interface/print
{".query":["type=ether","type=vlan","#|!"]}

等价于 API 语句:

/interface/print
?type=ether
?type=vlan
?#|!

下面的示例组合使用 query 和 proplist,为所有动态记录以及网络为 192.168.111.111 的记录返回 .id、address 和 interface 属性

$ curl -k -u admin: -X POST https://10.155.101.214/rest/ip/address/print \
  --data '{".proplist": [".id","address","interface"], ".query": ["network=192.168.111.111","dynamic=true","#|"]}'\
  -H "content-type: application/json"

[{".id":"*8","address":"10.155.101.214/24","interface":"sfp12"},
{".id":"*A","address":"192.168.111.111/32","interface":"dummy"}]

超时

如果命令无限期运行,它最终会超时,连接将因错误而关闭。当前超时间隔为 60 秒。为避免超时错误,请添加能够充分限制命令执行时间的参数。

例如,如果 ping 命令不添加 count 参数来限制其执行,就会超过超时时间:

$ curl -k -u admin: -X POST https://10.155.101.214/rest/ping \
  --data '{"address":"10.155.101.1"}' \
  -H "content-type: application/json"

{"detail":"Session closed","error":400,"message":"Bad Request"}

$ curl -k -u admin: -X POST https://10.155.101.214/rest/ping \
  --data '{"address":"10.155.101.1","count":"4"}' \
  -H "content-type: application/json"

[{"avg-rtt":"453us","host":"10.155.101.1","max-rtt":"453us","min-rtt":"453us","packet-loss":"0","received":"1","sent":"1","seq":"0","size":"56","time":"453us","ttl":"64"},
{"avg-rtt":"417us","host":"10.155.101.1","max-rtt":"453us","min-rtt":"382us","packet-loss":"0","received":"2","sent":"2","seq":"1","size":"56","time":"382us","ttl":"64"},
{"avg-rtt":"495us","host":"10.155.101.1","max-rtt":"650us","min-rtt":"382us","packet-loss":"0","received":"3","sent":"3","seq":"2","size":"56","time":"650us","ttl":"64"},
{"avg-rtt":"461us","host":"10.155.101.1","max-rtt":"650us","min-rtt":"359us","packet-loss":"0","received":"4","sent":"4","seq":"3","size":"56","time":"359us","ttl":"64"}]

对于接受 duration 参数的命令,REST 超时仍然适用。即使要求命令运行一小时,它也会提前终止并返回错误。

例如,带宽测试工具可以通过提供小于超时值的运行时长来限制:

$ curl -k -u admin: -X POST 'https://10.155.101.214/rest/tool/bandwidth-test' \
  --data '{"address":"10.155.101.1","duration":"3s"}' \
  -H "content-type: application/json"

[{".section":"0","connection-count":"20","direction":"receive","lost-packets":"0",
"random-data":"false","rx-10-second-average":"0","rx-current":"0","rx-size":"1500",
"rx-total-average":"0",
"status":"connecting"},
{".section":"1","connection-count":"20","direction":"receive","duration":"1s",
"lost-packets":"0","random-data":"false","rx-10-second-average":"0","rx-current":"0",
"rx-size":"1500","rx-total-average":"0",
"status":"running"},
{".section":"2","connection-count":"20","direction":"receive","duration":"2s",
"lost-packets":"581175","random-data":"false","rx-10-second-average":"854372352",
"rx-current":"854372352","rx-size":"1500","rx-total-average":"854372352",
"status":"running"},
{".section":"3","connection-count":"20","direction":"receive","duration":"3s",
"lost-packets":"9014","random-data":"false","rx-10-second-average":"891979008",
"rx-current":"929585664","rx-size":"1500","rx-total-average":"891979008",
"status":"done testing"}]

错误

API 调用的成功或失败由 HTTP 状态码指示。失败时(400 或更高),响应 body 包含一个 JSON 对象,其中含有错误码、描述和可选详情。例如,尝试删除一个接口会返回

{"error":406,"message":"Not Acceptable","detail":"no such command or directory (remove)"}

监控

REST API 不支持 monitor 之类的持续命令。请使用 monitor once 参数打印单个结果。

示例

下面整理了一小部分可以对设备发起的 REST API 调用:

写入一条日志:

curl -k -u <username>:<password> -X POST https://<ip-address>/rest/execute --data '{"script":"/log/info test"}' -H "content-type: application/json"

运行一个脚本:

curl -k -u <username>:<password> https://<ip-address>/rest/system/script/run --data '{".id":"*1"}' -H "content-type: application/json"

LTE monitor once:

curl -k -u <username>:<password> https://<ip-address>/rest/interface/lte/monitor -d '{"numbers":"0", "once":""}' -H "content-type: application/json"

导出设备配置:

curl -k -u <username>:<password> https://<ip-address>/rest/export --data '{"compact":"","file":"test.rsc"}' -H "content-type: application/json"

移动一条防火墙条目(交换位置):

curl -k -u <username>:<password> -X POST https://<ip-address>/rest/ip/firewall/nat/move --data '{".id":"*9","destination":"*C"}' -H "content-type: application/json"

更新 LTE 固件:

curl -k -u <username>:<password> -X POST 'https://<ip-address>/rest/interface/lte/firmware-upgrade'   --data '{"numbers":"lte2"}' -H "content-type: application/json"

从 /system/resource 菜单获取 OID:

curl -k -u <username>:<password> -X POST https://<ip-address>/rest/system/resource/print --data '{"oid":""}' -H "content-type: application/json"

使用 /tool/fetch 从一台 RouterOS 设备向另一台 RouterOS 设备发起 REST API POST:

/tool fetch http-method=post url="https://<ip-address>/rest/execute" \
http-data="{\"script\":\"/log info fetchtest\"}" \
http-header-field="Content-Type:application/json" \
output=user user=<username> password=<password>