在这篇文章中,我们将使用Zato作为一个多协议的Python API网关--我们将整合一些流行的技术,接受通过前端系统常用的协议发送的请求,丰富并传递给后端系统,并使用他们喜欢的数据格式向API客户返回响应。但首先,让我们定义一下什么是API网关。
理清术语
尽管我们今天晚些时候将重点讨论复杂的API集成,但要理解API网关这个术语,我们首先需要适当考虑网关这个术语。
当我们听到 "网关 "这个词时,我们想到的是,从词源上看确实是正确的,是在一个不允许的障碍物上开一个口。我们用门来进入在其他情况下由于各种原因而无法进入的地方。我们用它来离开这样一个地方。
事实上,"门 "和动词 "去 "都来自同一个基本词根,这再次让人想到一个概念,即通过专门留出的空间,以允许进入通常无法进入的地方。再一次,当我们离开这样一个区域时,我们也使用一个门。
从其真正的预期目的来看,一个让每个人都如实进出的大门,只不过是墙上的一个洞而已。换句话说,一个没有门的门不是全部的事情。
是的,从接近几乎所有中世纪或文艺复兴时期的门和大门所代表的建筑奇迹中,无疑可以得到巨大的审美满足,但我们知道,在当代,它们并没有按照最初的意图充分发挥其功能。
相反,我们可以凭直觉说,如果一个大门能让它的操作者实现以下目标,那么它就可以作为进入和离开的手段来使用,尽管不一定同时实现,这取决于一个人的特殊需要。
- 告诉到达者他们在哪里,包括投射出力量和自信
- 确认抵达者是他们所说的人
- 检查他们的原籍港是否友好
- 检查他们是否被允许进入受保护的地方
- 引导他们到网关后面的特定区域
- 保持长期和短期的入境记录
- 如果守门人知道答案,就在门边回答潜在的问题
- 与翻译和协调员合作,让抵达者在逗留期间使用他们所需要的东西。
我们现在可以认识到,网关是在内部和外部的边界上运作的,它本身是一个相对狭窄的,尽管可能是深刻的建筑。它是狭窄的,因为只有通过门户才有可能进入,但它可能更深或不深,取决于它应该为到达者提供多少东西。
我们也要记住,在同一时间很可能有不止一个网关存在,每个网关都有可能致力于不同的目的,有些是重叠的,有些不是。
最后,关键是要记住,网关是结构性的、建筑性的元素--网关应该做什么以及如何做,是留给建筑师的决定。
考虑到所有这些,我们很容易将对物理网关的理解转移到API网关应该是什么。
- API客户应该得到明确的信息,即他们正在进入一个限制区域
- 应检查源IP地址或其等价物,如果一个IP地址或等价物信息不在允许的范围内,则拒绝请求
- 网关应检查用户名、密码、API密钥和类似的表示方式。
- 应检查访问后端系统的权限,因为不是每个API客户都能访问所有东西。
- 请求应被派往相关的后端系统
- 请求和响应应该以不同的格式记录下来,有些是供程序和应用阅读的,有些是供人类操作的。
- 如果适用的话,可以从网关的缓存中提供响应,以减轻后端系统的负担。
- 请求和响应可以被转化或充实,这可能意味着在API调用者收到响应之前要联系多个后端系统。
我们现在可以将API网关定义为系统架构中的一个元素,它肯定与安全、权限以及授予或拒绝对后端系统、应用程序和数据源的访问有关。在此基础上,它可以提供审计、数据转换和缓存服务。这个定义在某种程度上总是不稳定的,取决于建筑师的愿景,但这是可以预期的。
在定义了什么是API网关之后,让我们在Zato和Python中创建一个API网关。
客户端和后端系统
在这篇文章中,我们将整合两个前端系统和一个后端应用程序。前端系统将使用REST和WebSockets,而后端系统将使用AMQP。Zato将充当它们之间的API网关。

不允许前端API客户端直接访问后端系统通常是一个好主意,因为两边的系统创建所涉及的动态通常非常不同。但他们仍然需要沟通,因此需要使用Zato作为API网关。
Python代码
首先,让我们展示一下整合我们架构中的系统所需的Python代码。
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class APIGateway(Service):
""" Dispatches requests to backend systems, enriching them along the way.
"""
name = 'api.gateway'
def handle(self):
# Enrich incoming request with metadata ..
self.request.payload['_receiver'] = self.name
self.request.payload['_correlation_id'] = self.cid
self.request.payload['_date_received'] = self.time.utcnow()
# .. AMQP configuration ..
outconn = 'My Backend'
exchange = '/incoming'
routing_key = 'api'
# .. publish the message to an AMQP broker ..
self.out.amqp.send(data, outconn, exchange, routing_key)
# .. and return a response to our API client.
self.response.payload = {'result': 'OK, data accepted'}
有几个值得注意的地方。
-
网关服务用元数据来充实传入的请求,但它也可以用业务数据来充实它,例如,它可以与另一个系统通信以获得所需的信息,然后才将请求传递给最终的后端系统。
-
在目前的形式下,我们只将所有的信息发送给AMQP中介,但我们也可以将其发送给其他系统,并可能在途中对请求进行修改。
-
代码是非常抽象的,它目前的所有配置都可以移到配置文件、Redis或其他数据源中,使其更加高级。
-
安全配置和其他细节没有直接在网关服务的主体中声明,但它们需要存在于某个地方--我们将在下一节中描述它
配置
在Zato中,API客户端使用通道访问平台的服务--那么让我们为REST和WebSockets创建一个通道。
首先是REST。

现在是WebSockets。

我们以同样的方式创建一个新的出站AMQP连接。

使用API网关
在这一点上,网关已经准备好了--你可以从REST或WebSockets调用它,它收到的任何JSON数据将被网关服务处理,AMQP代理将收到它,API客户将从网关得到JSON响应。
让我们使用curl来调用REST通道,输入JSON有效载荷。
$ curl http://api:@localhost:11223/api/v1/user ; echo
curl --data-binary @request.json http://localhost:11223/api/v1/user ; echo
{"result": "OK, data accepted"}
$
综合来看,通道和服务让我们实现了这一点。
- 多个API客户端可以访问后端AMQP系统,每个客户端使用自己的首选技术
- 在服务开始处理请求之前,在输入时检查客户的证书(认证)。
- 可以给客户分配RBAC角色,这样就可以确保他们只能访问后端API的选定部分(授权)。
- 信息日志记录了传入和传出的数据
- 渠道的响应可以被缓存,这减少了后端系统的负担。
- 接受请求的服务可以自由地以业务逻辑所要求的任何方式修改、充实和转换数据。例如,在上面的代码中,我们只添加了元数据,但我们也可以在请求被发送到预定的接收者之前与其他应用程序联系。
我们可以更进一步。例如,网关服务目前对请求的实际内容完全不闻不问。
但是,由于我们在self.request.payload中只有一个普通的Python dict,我们可以不费吹灰之力地修改服务,将请求分配给不同的后端系统,这取决于请求所包含的内容或可能是其他后端系统决定的目的地。
这种额外的逻辑是针对每个环境或项目的,这就是为什么这里没有显示,这也是为什么我们在这一点上结束了文章,但它的中心部分已经完成了,剩下的只是定制和插入更多的API客户端的通道或后端系统的出站连接的问题。
最后,在多个网关之间分割对系统的访问是完全可以的--每个网关一方面可以处理来自选定技术的请求,但另一方面,每个网关可能使用不同的缓存或速率限制策略。如果有一个以上的网关,在每个网关的基础上配置这些细节可能更容易。