代码之家  ›  专栏  ›  技术社区  ›  James

按每个用户的请求总数限制API端点

  •  1
  • James  · 技术社区  · 6 年前

    我目前正在探索解决方案,通过每月请求总数限制对nodejs上API端点的访问。

    例如,我希望自由计划用户访问 /api 终结点每月最多100个请求,高级计划用户每月有5000个请求。

    解决这个问题的方法是通过实施Passport中间件来获取用户的计划,然后跟踪计数:

      app.get("/api", requireAuth, async (req, res, next) => {
        try {
            // Check if user ran out of requests
            if (req.user.apiRequestsLeft === 0) {
              res.send("You ran out of API requests!")
            } else {
              // Decrement the allocated requests
              req.user.apiRequestsLeft--;
              await req.user.save();
              res.send(user)
            }
        } catch (err) {
          next(err);
        }
      });
    

    我担心的是:

    1. 每次有请求时都必须更新MongoDB文档的性能/可伸缩性问题-这是可行的还是在应用程序增长时遇到问题?
    2. 重新设置计数-这是一个每日的cronjob,它查看每个用户的“注册”时间戳,计算是否一个月过去,并相应地重新设置分配的请求,还是有更好的方法来设计类似的东西?
    1 回复  |  直到 6 年前
        1
  •  2
  •   Anand Undavia    6 年前

    必须更新MongoDB文档的性能/可伸缩性问题 每次有请求时——这是可行的还是我会遇到问题? 当应用程序增长时?

    一定地。您很快就会遇到大量MongoDB流量,它将遇到性能瓶颈。在我看来,您应该使用更快的内存数据库,比如 Redis 处理好情况。您甚至可以使用redis作为 session-store 这将减少MongoDB上的负载。这样,MongoDB就可以用于其他业务查询。

    重置计数-这是否应该是一个每天查看 每个用户的“注册”时间戳,计算一个月 已相应地传递并重置分配的请求,或者是否存在 更好的设计方法?

    更好的方法是在中间件本身中实现重置部分。

    下面是一些解释我的解决方案的代码。

    样品设计 Quota 对象为:

    {
        type: "FREE_USER",                  /** or "PREMIUM_USER" */
        access_limit: 100,                  /** or 5000 */
        exhausted_requests: 42              /** How many requests the user has made so far this month */
        last_reset_timestamp: 1547796508728 /** When was the exhausted_requests set to 0 last time */
    }
    

    用那种设计。检查配额的中间件如下所示:

    const checkQuota = async (req, res, next) => {
        const user = req.user;
        const userQuotaStr = await redis.getAsync(user.id)
        let userQuota;
        /** Check if we have quota information about user */
        if (userQuotaStr != null) {
            /** We have previously saved quota information */
            userQuota = JSON.parse(userQuotaStr);
    
            /** 
             * Check if we should reset the exhausted_requests
             * Assuming that all the requests are reset on the First Day of each month.
             */
            if ( isStartOfMonth() ) {
                /** 
                 * It is First Day of the month. We might need to reset the `exhausted_requests` 
                 * Check the difference between `Date.now()` and `userQuota.last_reset_timestamp`
                 * to determine whether we should reset or not
                 */
                if ( shouldResetTimeStamp(userQuota.last_reset_timestamp) ) {
                    userQuota.exhausted_requests = 0
                    userQuota.last_reset_timestamp = Date.now()
                }
            }
        } else {
            /** We do not have previously saved quota information. Prepare one */
            userQuota = {
                type: user.type,
                access_limit: user.access_limit,
                exhausted_requests: 0,
                last_reset_timestamp: Date.now()
            }
        }
    
        /** Incredement the counter to account the current request */
        userQuota.exhausted_requests++
    
        /** Update in database */
        redis.set(user.id, JSON.stringify(userQuota))
    
    
        if ( userQuota.exhausted_requests >= userQuota.access_limit ) {
            /** User has reached the quota limit. Deny the request. set with 401 or 403 status code */
        } else {
            /** User can access the API. call next() */
        }   
    }
    

    当然,这段代码是不完整的。它只是让您了解如何编写中间件。

    以下是如何为API使用中间件:

    /** If requests to routes are under the quota */
    app.get("/api/quota-routes", requireAuth, checkQuota, /** Mount the actual middleware here */)
    
    /** If requests to routes are unlimited, just remove the checkQuota middleware */
    app.get("/api/unlimited-routes", requireAuth, /** Mount the actual middleware here */)