API网关,是现代微服务架构中扮演重要角色的一部分。它作为所有客户端请求的入口,管理、验证和清理请求,并将其路由到适当的服务。在这个系列中,我将尝试使用Node.js来实现API网关的重要部分。在这篇文章中,我将首先提供API网关的简要介绍,讨论其优点和缺点,并重点实现我自己的API网关中路由组件的实现。
概览:API网关是一个充当所有客户端请求入口点的服务器。它可能负责如下:
- 路由功能:它根据请求路径、方法、头部和其他参数将客户端传入的请求路由到正确的微服务;
- 安全:它可能强制执行特定的安全策略,例如认证和授权,确保只有授权的客户端才能访问某些服务或端点;
- 速率限制:它可能限制客户端在一定时间内可以发起的请求数量,以防止微服务因太多请求而过载;
- 缓存:它可以缓存微服务的响应,减少重复请求的处理;
- 监控与日志记录:它提供详细的API调用监控和日志记录,为微服务的使用情况、性能和健康状况提供有价值的洞察。
使用 API 网关的一些好处包括:
- 简化客户端通信:客户端不再与多个服务进行通信,而是仅与API网关通信,从而减少集成复杂度;
- 集中化安全措施:您可以在一个位置确保身份验证和授权,从而更容易管理新策略;
- 服务抽象:API网关隐藏了底层服务的复杂性,使得对单个服务的修改和升级更加简便;
- 协议无关性:底层服务可以使用各种协议,例如GraphQL、gRPC等进行通信,而API网关则提供统一的HTTP接口,简化了集成过程。
不过,API Gateway(即API网关)存在一些问题,比如:
- 单一故障点:如果API网关失败,可能导致整个API服务瘫痪;
- 增加延时:在请求和响应路径中增加一层可能会引入延迟;
- 开发负担:开发自定义路由、安全策略等可能需要不小的努力和专业知识。
因此,你需要小心地采用特殊的架构实践,以确保API网关的高可用性、可扩展性和弹性。因为API网关是你公开的API的核心组件。
如果你还没有准备好应对自己构建 API 网关所带来的开发负担,你可能需要考虑使用以下产品中的一种:
- AWS API网关 :由 Amazon 提供的管理型服务,允许用户创建、维护并在任何规模上发布API网关;
- Kong :开源的 API 网关和应用管理平台,提供负载均衡、监控、日志记录和认证;
- Nginx :一款高性能的开源 Web 服务器,也可以用作 API 网关;
- Traefik :HTTP 反向代理和负载均衡器,也可以用作 API 网关。
为了展示API网关是如何工作的,我将创建一个简单的演示,首先展示网关的各种功能。我们将首先专注于路由功能。
路由API网关的主要任务是将入站流量路由到各个微服务。为了正确路由入站流量,API网关需要一个配置来。你可以通过两种方式来配置API网关:
- 静态配置:以 JSON 或 YAML 等格式的文件形式提供配置。
- 动态配置:这种做法允许 API 网关实时从数据库或如服务发现这样的集中式服务获取配置。
动态配置对经常变化的环境非常有利。然而,它也会增加API网关开发的复杂性,同时可能造成性能损耗,因为API网关需要定期拉取并应用配置变更。
为了简单点,我将用一个简单的 JSON 文件来设置 API 网关路由。
受Webpack的代理配置启发,我定义了如下的API Gateway配置:
[
{
"name": "user-service",
"context": ["/users"], // 用户上下文:/users
"target": "http://localhost:3001", // 目标地址:本地运行的用户服务,端口为3001
"pathRewrite": {} // 路径重写:无
}
]
配置是一系列路由。每个路由包括以下属性:
**name**
:路由的唯一标识符,用于追踪目的;**context**
:包含API网关需要监听的具体路由,当请求的URL以/users
开头,API网关将请求路由到相应的后端服务;**target**
:定义API网关应将匹配指定上下文的请求转发到的后端服务的URL地址;**pathRewrite**
:允许在请求URL路径被转发到目标服务之前进行修改的属性。如果配置为空对象({}
),则表示不需要进行任何转换操作。但配置{‘^/test’: ‘/api’}
表示路径/test
将被替换为/api
。
为了用 Node.js 实现 API Gateway,我将使用 Express Web 服务器。它是一个灵活、简单且强大的 Web 服务器,非常适合构建 API 网关。
根据配置,API 网关可能会只监听配置文件中定义的特定路由路径。不过,我打算让它监听所有传入的流量。如果在配置文件中没有定义某个路由,网关会返回一个 404 Not Found
响应并附带解释。
让我们来实现一个处理传入请求的程序。
const fetch = require('node-fetch')
const routes = require('../routes/routes.json')
const errors = {
ROUTE_NOT_FOUND: 'ROUTE_NOT_FOUND',
}
const defaultTimeout = parseInt(process.env.HTTP_DEFAULT_TIMEOUT) || 5000
const nonBodyMethods = ['GET', 'HEAD']
/**
* @typedef {import('node-fetch').Response} Response
* @typedef Result
* @property {string} error
* @property {string} message
* @property {Response} response
*/
/**
* 此方法用于代理请求到适当的服务
* @typedef {import('express').Request} Request
* @param {Request} req
* @returns {Promise<Result>}
*/
async function handler(req) {
const { path, method, headers, body, ip } = req
const route = routes.find((route) =>
route.context.some((c) => path.startsWith(c))
)
if (!route) {
return {
error: errors.ROUTE_NOT_FOUND,
message: 'route not found',
}
}
const servicePath = Object.entries(route.pathRewrite).reduce(
(acc, [key, value]) => acc.replace(new RegExp(key), value),
path
)
const url = `${route.target}${servicePath}`
const reqHeaders = {
...headers,
'X-Forwarded-For': ip,
'X-Forwarded-Proto': req.protocol,
'X-Forwarded-Port': req.socket.localPort,
'X-Forwarded-Host': req.hostname,
'X-Forwarded-Path': req.baseUrl,
'X-Forwarded-Method': method,
'X-Forwarded-Url': req.originalUrl,
'X-Forwarded-By': 'api-gateway',
'X-Forwarded-Name': route.name,
'X-Request-Id': req.id,
}
const reqBody = nonBodyMethods.includes(method) ? undefined : body
const response = await fetch(url, {
method,
headers: reqHeaders,
body: reqBody,
follow: 0,
timeout: route?.timeout || defaultTimeout,
})
return {
response,
}
}
module.exports = handler
module.exports.errors = errors
处理程序的第一步是找到与传入路径相匹配的配置。如果路由未在配置中定义,将返回一个错误。此时,另一个处理程序(我后面会演示)将向用户返回404 Not Found
响应。
接下来,API网关应用路径重写规则并准备HTTP请求以转发到目标服务。在此过程中,API网关还会添加定义代理环境的自定义头部。其中可能包含关于原始请求的信息,例如客户端的IP地址,以及其他必要的元数据。
目标服务通过HTTP响应消息返回数据至Express中间件。
const service = require('../services/proxy')
/**
* 一个代理处理器,用于处理 Express 请求和响应
* @typedef {import('express').Request} Request
* @typedef {import('express').Response} Response
* @typedef {import('express').NextFunction}
* @param {Request} req
* @param {Response} res
* @returns {void}
*/
module.exports = async (req, res, next) => {
try {
const { error, message, response } = await service(req)
if (error) {
if (error === service.errors.ROUTE_NOT_FOUND) {
res.status(404).json({ error, message }).send()
} else {
res.status(500).json({ error, message }).send()
}
return
}
res.status(response.status)
response.headers.forEach((value, key) => res.setHeader(key, value))
const content = await response.buffer()
res.send(content)
} catch (err) {
next(err)
}
}
如果返回了错误,处理程序会分析这个错误并回复一个合适的响应给用户。否则,它会将目标系统的响应转发给用户。
一开始,我也实现了一些中间件:
- 错误处理器:这个错误处理器记录在处理请求时发生的任何错误;
- 日志记录器:这个日志记录器记录有关传入流量的日志;
- 请求ID中间件:这个请求ID中间件为每个传入请求添加一个唯一的标识符以便跟踪。
API网关不仅限于处理JSON数据,它还可以接受和返回包括XML、HTML、纯文本和二进制数据在内的多种数据格式。为了确保API网关能够处理所有类型的内容,我已经配置了Express的原始数据中间件来处理所有内容类型,以确保API网关能够处理任何内容类型。
此外,我还通过单元测试来确保代理处理程序的可靠性和准确性。我还使用GitHub Actions实现了CI流程,以实现自动测试。这样设置确保任何代码库的更改都会自动进行测试,从而保持高质量的代码。
为了测试 API 网关,我实现了一个简单的用户相关服务。
const express = require('express');
const app = express();
const users = []
app.use(express.json());
app.get('/users/:id', (req, res) => {
const id = req.params.id;
const user = users.find(user => user.id === id);
res.json(user);
});
app.post('/users', (req, res) => {
const user = req.body;
users.push(user);
res.json(user);
})
app.listen(3001, () => {
console.log('服务器正在端口3001上运行');
})
你可以通过执行以下请求来访问用户服务:
通过 API 网关访问用户服务的步骤如下:
使用curl命令向localhost:3000/users
发送一个POST请求,并添加如下的头部和数据:
--header 'Content-Type: application/json' \
--data '{
"id": "42",
"name": "Dmytro"
}'
该命令用于向服务器发送用户信息,包括ID和姓名。接下来,使用curl命令向localhost:3000/users/42
发送一个GET请求。
你可以在这个仓库里找到代码。
GitHub - misikdmytro/api-gateway 在 GitHub 上通过创建一个帐户来帮助开发 misikdmytro/api-gateway。如果你喜欢这个仓库,别忘了把它启动起来!
给我加油如果你喜欢我的文章,并想要表示一些感谢,现在你可以通过“Buy Me A Coffee”(买我一杯咖啡)来支持我。你的支持将对我继续分享更多让你感兴趣的故事和内容帮助很大。
Dmytro Misik 是来自 🇺🇦 的技术经理 👨💻,现在在 DraftKings 工作,并且是 Medium 贡献者。 结论部分API网关在现代微服务开发中起到关键作用,它作为所有传入客户端请求的单一入口,将请求路由到相应服务,并管理如安全性、速率限制等重要方面。
在这篇文章里,我开始用Node.js和Express来搭建一个API网关。通过使用中间件和适当的配置,我们创建了一个灵活且可扩展的网关,。我们创建了一个灵活且可扩展的网关,简化了客户端与后端服务间的交互。此外,通过实现单元测试和CI流程,确保了我们的API网关既可靠又代码质量高。
未来的帖子将探讨 API 网关的其他方面,比如安全、监控、限流等。敬请期待更多见解和实用示例,帮助您更好地掌握使用 Node.js 进行 API 网关开发的技巧。
共同学习,写下你的评论
评论加载中...
作者其他优质文章