为了账号安全,请及时绑定邮箱和手机立即绑定

用Node.js构建API网关:第一部分——概览与路由

标签:
Node.js 架构 API
了解API网关架构的基本概念

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 网关开发的技巧。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消