<返回更多

如何循序渐进地管理RESTful API的生命周期?

2022-08-29    鸨哥学Java
加入收藏

设计一个直观且用户友好的RESTful API往往是一项艰巨的工作。而对于初次尝试规划和管理API生命周期的新手开发者而言,尤为如此。下面,我将以简单示例的形式,和您探讨如何循序渐进地管理RESTful API的生命周期。

 

初始阶段

让我们首先来看一个典型的Hello应用代码的示例:

> curl http://org. apisix/hello
Hello world
> curl http://org. apisix/hello/Joe
Hello Joe

如下图所示,我们不必了解其底层技术,只需专注其API部分即可。

 

采用API网关

首先也是最关键的一步:禁止将应用直接暴露到互联网上,并在前端建立一个API网关。维基百科是这样定义API网关的:作为一个API的服务器前端,它能够收到各种API请求,并通过实施节流和安全策略,将请求传递给后端服务,进而将响应回传给请求者。

网关通常包括一个用于编排和修改请求与响应转换引擎。同时,网关还可以提供诸如:收集分析数据、提供缓存、支持身份验证、授权、安全审计以及合规检查等功能。当然,如果您不太熟悉API网关的概念,也可以直接把它理解为一个更高级别的反向代理。在此,我将使用 Apache APISIX​ ​,您也可以使用自己熟悉的网关。

为了暴露网关,您需要更新指向网关的DNS记录,并向外广播。您既可以等待一段时间让其自动更新,也可以通过​ ​dnschecker​ ​来加速这个过程。

我使用APISIX创建了一个将HTTP请求发送到网关的路由(route,请参见如下代码段)。

curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1-2
{
  "name": "Direct Route to Old API",               # 3
  "methods": ["GET"],                              # 4
  "uris": ["/hello", "/hello/", "/hello/*"],       # 5
  "upstream": {                                    # 6
    "type": "roundrobin",                          # 8
    "nodes": {
      "oldapi:8081": 1                             # 7
    }
  }
}'

注释:

1. APISIX会分配一个自动生成的ID(您也可以使用现成的)。在此,我使用现成的,并使用put将它传递给URL - 1。

2. 通过API key来更新路由。

3. 虽然我们不一定需要命名路由,但它能够让我们更好地对其有所了解。

4. 针对路由的HTTP方法数组。

5. 针对路由的URL数组。

6. 指明后端应用的上游(upstream)。在本例中,即为Hello World API。

7. 各个节点的Hashmap都自带有权重。显然,权重只有存在多个节点时才有意义。

8. 针对多个节点配置均衡算法。

 

如上图所示,您可以通过如下方式查询网关,并得到相同的结果:

> curl http://org. apisix/hello
Hello world
> curl http://org. apisix/hello/Joe
Hello Joe

API的版本

开发一个API往往意味着会出现其多个版本共存的情况。我们可以用如下三种方式给API编制版本:

curl http://org. apisix/hello?version=1

curl http://org. apisix/hello?version=2

curl -H 'Version: 1' http://org. apisix/hello

curl -H 'Version: 2' http://org. apisix/hello

curl http://org. apisix/v1/hello

curl http://org. apisix/v2/hello

在此,我将使用目前广为采用的基于路径的版本编制方法。当然,APISIX也支持其他两种方式。

在前文中,我们创建了一个包含上游信息的路由。同时,APISIX也允许我们创建一个带有专属ID的上游,以重用其多个路由:

curl http://apisix:9080/apisix/admin/upstreams/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1
{
  "name": "Old API",                                                             # 2
  "type": "roundrobin",
  "nodes": {
    "oldapi:8081": 1
  }
}'

注释:

1. 使用upstreams路径

2. 针对新的上游的有效载荷

由于上游只知道/hello,不知道/v1/hello,因此在转发至上游之前,我们仍需要重写发往网关的查询。APISIX可以通过插件来实现此类转换和过滤。下面,让我们创建一个用于重写路径的插件配置:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1
{
  "plugins": {
    "proxy-rewrite": {                                        # 2
      "regex_uri": ["/v1/(.*)", "/$1"]                        # 3
    }
  }
}'

注释:

1. 使用plugin-configs路径

2. 使用​ ​proxy-rewrite​ ​插件

3. 删除版本的前缀

现在我们就可以通过如下代码段,创建有版本的路由,以引导新创建的上游和插件配置:

curl http://apisix:9080/apisix/admin/routes/2 -H 'X-API-KEY: xyz' -X PUT -d '  # 1
{
  "name": "Versioned Route to Old API",
  "methods": ["GET"],
  "uris": ["/v1/hello", "/v1/hello/", "/v1/hello/*"],
  "upstream_id": 1,
  "plugin_config_id": 1
}'

下面展示了新路由的逻辑图。

 

在此,我们已配置了有版本和非版本的两个路由:

> curl http://org. apisix/hello
Hello world
> curl http://org. apisix/v1/hello
Hello world

将用户从非版本路径迁移到有版本路径

虽然我们给API编制了版本,但是用户可能仍会使用旧的、非版本的API。毕竟,我们肯定不能在用户不知情时就删除掉旧的路由,而只能通过HTTP的301状态代码,让用户知道资源已经从http://org.apisix/hello迁移到了

http://org.apisix/v1/hello。如下代码段展示了​ ​如何通过配置重定向插件​ ​来初始化路由:

curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
  "plugins": {
    "redirect": {
      "uri": "/v1$uri",
      "ret_code": 301
    }
  }
}'

 

其运行结果如下:

>curl http://apisix. org/hello
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty</center>
</body>
</html>
>curl -L apisix:9080/hello                     # 1
Hello world

注释:

1. -L选项后面跟着重定向

根据规则,用户要么透明地使用到了新的端点,要么收到301的状态提示,使用新的API位置。

识别用户

您可能已经注意到,我们并不知道谁会使用我们的API。因此,为了保证API在被调用的过程中不会中断用户的使用,我选择了限制未注册的用户可调用的数量。如果他们的总量到达了该限制,我们将返回典型的HTTP 429状态消息,要求他们完成注册。

当然,目前尚无开箱即用的插件可以实现这一点。因此,我在APISIX中引入Lua引擎,并用Lua编写出了相应的插件。您可以通过GitHub的链接

--https://github.com/nfrankel/evolve-apis/blob/master/unauth-limit-plugin/src/unauth-limit.lua,来浏览其源代码。当然,您也可以使用Python/ target=_blank class=infotextkey>Python、WebAssembly或任何基于JVM的语言,来编写自己的插件。

我将通过如下步骤来完成插件的加载:

1. 配置APISIX使用的目录:

apisix: extra_lua_path:/opt/apisix/.lua ?”

注意:APISIX可以使用位于/opt/apisix/文件夹下的任何Lua脚本。

2. 加载插件:

由于APISIX支持热重载,因此它可以在无需重新启动的情况下添加额外的插件。

curl http://apisix:9080/apisix/admin/plugins/reload -H 'X-API-KEY: xyz' -X PUT

3. 给现有插件的配置打补丁:

最后,我们需要为插件本身更新专属的配置:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
  "plugins": {
    "proxy-rewrite": {                                # 1
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {                                 # 2
      "count": 1,                                     # 3
      "time_window": 60,                              # 3
      "key_type": "var",                              # 4
      "key": "consumer_name",                         # 4
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    }
  }
}'

注释:

1. 我们需要重复现有的插件配置。当然,APISIX团队正在修复这个bug。

2. 指向我们的插件。

3. 对于已通过认证的用户,插件可限制每60秒超过一个调用。

4. 我会在下一节中解释到。

我们通过如下命令检查其是否能够按预期运行:

>curl apisix:9080/v1/hello
Hello world
>curl apisix:9080/v1/hello
{"error_msg":"Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"}

实际上确实如此。

用户认证

接着,我们需要为消费者(consumer)角色配置一个专属的身份验证插件。目前,我们可以选用的此类身份验证插件有:API key、JWT、OpenId、LDAP、以及Keycloak等。在本例中,我们采用APISIX的​ ​key-auth​ ​插件。下面,让我们配置一个通过API key验证的消费者对象:

curl http://apisix:9080/apisix/admin/consumers -H 'X-API-KEY: xyz' -X PUT -d '
{
  "username": "johndoe",                 # 1
  "plugins": {
    "key-auth": {                        # 2
      "key": "mykey"                     # 3
    }
  }
}'

注释:

1. 消费者的ID

2. 可供使用的插件

3. 有效令牌--mykey

注意,其默认的标头为apikey,你也可以配置为其他,具体请参见key-auth插件的​ ​相关文档​ ​。

我们用如下命令来验证其是否能够按照我们的需求运行:

>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world
>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world

在生产环境中测试

至此,我们的改进版Hello world API便可以供用户调用了。如您所见,部署一个新的、可能包含潜在错误的应用版本,往往会给生产环境和业务营收带来负面的影响。为了尽量减少此类风险,我们可以采用金丝雀发布策略,即:先对一小部分用户推出新的软件版本,然后慢慢地扩展到生产环境中的所有用户处。如果出现了故障,那么新版本只会影响一小部分的用户群,我们能够及时回滚到旧的版本。就API网关而言,我们可以复制生产环境的流量到新的API端点上,实现在对用户几乎不产生影响的情况下,尽早发现更多的缺陷。

在此,APISIX提供了​ ​proxy-mirror插件​ ​,可向其他节点发送复制的生产环境流量。对此,我们可以对插件配置做如下更新:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "proxy-mirror": {
      "host": "http://new. api:8082"                             # 1
    }
  }
}'

注释:

1. APISIX将发送流量到该地址上。

 

根据上图,我们可以监控和比较新和旧端点,一旦发生错误,我们便可以修复错误,并重新部署。在此,我们首先通过如下命令,创建一个指向新的API的上游:

curl http://apisix:9080/apisix/admin/upstreams/2 -H 'X-API-KEY: xyz' -X PUT -d '
{
  "name": "New API",
  "type": "roundrobin",
  "nodes": {
    "newapi:8082": 1
  }
}'

然后,我们可以使用​ ​traffic-split​ ​,来更换proxy-mirror插件:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "traffic-split": {
      "rules": [
        {
          "weighted_upstreams": [      # 1
            {
              "upstream_id": 2,
              "weight": 1
            },
            {
              "weight": 1
            }
          ]
        }
      ]
    }
  }
}'

注释:

1. 作为演示,我们将50%的流量发送到新的API。在真实环境中,您可能只会配置少数的内部用户去使用新的端点。

curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world
curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world (souped-up version!)

如果一切工作正常,我们可以将逐渐增加的流量移至新的API。最终我们可以移除traffic split,将默认端点从v1重定到v2上。

弃用旧的版本

根据IETF的草案规范,我们可以基于特定的HTTP响应标头,来弃用HTTP的标头字段 (请参见

--https://tools.ietf.org/id/draft-dalal-deprecation-header-03.html)。即:在API网关的帮助下,我们可以通过配置路由,来与有待弃用和替代的版本进行通信。为此,我们将使用由APISIX提供​ ​response-rewrite​ ​,来添加额外弃用标志标头,请参见如下代码段:

curl -v http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "response-rewrite": {
      "headers": {
        "Deprecation": "true",
        "Link": "<$scheme://apisix:$server_port/v2/hello>; rel="successor-version""
      }
    }
  }
}'
curl -v -H 'apikey: mykey' apisix:9080/v1/hello
< HTTP/1. 1 200 
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Connection: keep-alive
< Date: Fri, 18 Feb 2022 16:33:30 GMT
< Server: APISIX/2. 12. 0
< Link: <http://apisix:9080/v2/hello>; rel="successor-version"
< Deprecation: true
< 
Hello world

小结

回顾一下,我们按照如下流程向您展示了如何通过循序渐进的过程,来管理API的整个生命周期。

1. 不要直接暴露您的API,而是在前端建立一个API网关

2. 使用路径、查询参数、以及请求标头给现有的API编制版本

3. 通过使用301状态码,将用户从无版本的端点迁移到有版本处

4. 识别和认证用户

5. 为了测试生产环境,我们先复制流量,再将小部分用户迁移到新的版本上

6. 发布新的版本

7. 通过标准响应标头来弃用旧的版本

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>