Python3 writes an http interface service (url, get, post), interface current limit, access denied

Python3 writes an http interface service (url, get, post), interface current limit, access denied

http(url) interface current limit/deny access/limit queue

Interface rate limit (rate-limit). In my recent work, the author encountered algorithmic microservices provided to customers, and encountered a large number of requests to hang up. In addition to expanding the load balance, the rate-limiting method was also adopted.
Generally speaking, for common interface current limiting, we can use

  • a. (Guardian and security) WEB server/reverse proxy server (c language), Nginx/Apache, etc. support high concurrent load balancing, intercept static requests, SSL support, and reserve port 80;
  • b. (Operation and management) WEB servers (wsgi, http, python), Gunicorn/uWsgi, etc. support multi-process and multi-thread, fail restart, receive request distribution (call service);
  • c. (work and business) WEB service framework (lightweight, python), Fastapi/flask/Tornado, etc. support back-end applications, single service, lightweight concurrency;

general structure

(nginx) -> (gunicorn) -> fastapi

Current limiting method

  • a. Nginx configuration current limit, its built-in leaky bucket algorithm, token bucket algorithm, can configure limit_req_zone/limit_req/limit_rate (burst, nodelay) and other modules to limit current;
  • b. Gunicorn configures current limiting, its built-in counter, configurable queue length, etc. to deny access, and gunicorn-proxy can be used to request buffering and shunting;
  • c. FastAPI configuration current limit, configurable counter (memory cache), semaphore (redis cache), etc., count (time, IP) to limit access;

fastapi + slowapi current limiting

# !/usr/bin/python
# -*- coding: utf-8 -*-
# @time    : 2023/02/01 21:07
# @author  : Mo
# @function: 限流(slowapi), http of fastapi


import time

from fastapi import FastAPI, Request, Response, File, UploadFile
from starlette.responses import JSONResponse
from pydantic import BaseModel
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
from slowapi import Limiter


def _rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded) -> Response:
    """
    Build a simple JSON response that includes the details of the rate limit
    that was hit. If no limit is hit, the countdown is added to headers.
    """
    response = JSONResponse({"error": f"Rate limit exceeded: {exc.detail}; status_code: {ERROR_CODE}; "
                                      f"request: {request.base_url}"}, status_code=ERROR_CODE)
    response = request.app.state.limiter._inject_headers(response, request.state.view_rate_limit)
    return response


LIMIT = "{}/second".format(5*2)  #  ("5/day") ("5/hour") (5/minute) ("5/second")  # 需放在@app.后
ERROR_CODE = 429

limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)


class Item(BaseModel):
    a: int = None
    b: int = None


@app.post("/calculate")
@limiter.limit(LIMIT)  #  ("5/day") ("5/hour") (5/minute) ("5/second")  # 需放在@app.后
async def calculate(item: Item, request: Request):  # Request必须配置
    a = item.a
    b = item.b
    c = a + b
    res = {"code": 200, "res": c}
    time.sleep(0.05)
    return res


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app=app,
                host="0.0.0.0",
                port=8832,
                workers=1)

test

# !/usr/bin/python
# -*- coding: utf-8 -*-
# @time    : 2020/7/9 14:14
# @author  : Mo
# @function: test 


import requests, time, json, threading, random
import pandas as pd


def get_variable_name():
    """ 获取 string 变量的变量名字, variable_name: str """
    import inspect
    variable_name_dict = dict(inspect.currentframe().f_locals.items())
    res = None
    for k, v in variable_name_dict.items():
        if "frame" in str(type(v)):
            res = k
            break
    return res


class PressTet(object):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
        'Content-Type': 'application/json; charset=UTF-8',
        'Connection': 'close',
    }

    def __init__(self, press_url, data_json):
        self.press_url = press_url
        self.session = requests.Session()
        self.session.headers = self.headers
        self.data_json = data_json

    def work(self):
        '''压测接口'''
        time_str = str(time.time()) + str(random.random())
        self.data_json["time"] = time_str  # 加个参数使得每次传参不一样
        global ERROR_NUM
        try:
            html = self.session.post(self.press_url, data=json.dumps(self.data_json))
            html_json = html.json()
            # 错误判断
            if html_json.get("code", 0) != 200:
                print(html.json())
                ERROR_NUM += 1
            html.close()
        except Exception as e:
            print(str(e))
            ERROR_NUM += 1

    def tet_onework(self):
        '''一次并发处理单个任务'''
        i = 0
        while i < ONE_WORKER_NUM:
            i += 1
            self.work()
        time.sleep(LOOP_SLEEP)

    def run(self):
        '''使用多线程进程并发测试'''
        t1 = time.time()
        Threads = []

        for i in range(THREAD_NUM):
            t = threading.Thread(target=self.tet_onework, name="T" + str(i))
            t.setDaemon(True)
            Threads.append(t)

        for t in Threads:
            t.start()
        for t in Threads:
            t.join()
        t2 = time.time()

        print("===============压测结果===================")
        print("URL:", self.press_url)
        print("任务数量:", THREAD_NUM, "*", ONE_WORKER_NUM, "=", THREAD_NUM * ONE_WORKER_NUM)
        print("总耗时(秒):", t2 - t1)
        print("每次请求耗时(秒):", (t2 - t1) / (THREAD_NUM * ONE_WORKER_NUM))
        print("每秒承载请求数:", 1 / ((t2 - t1) / (THREAD_NUM * ONE_WORKER_NUM)))
        print("错误数量:", ERROR_NUM)


if __name__ == '__main__':


    # 在work里边设置一下错误判断, work
    press_url = 'http://localhost:8832/calculate'
    data_sample = {"a": 12, "b": 32}

    THREAD_NUM = 50  # 并发线程总数, 可设置 10, 20, 30, 50...
    ONE_WORKER_NUM = 10  # 每个线程的循环次数,  可设置 10, 20, 30, 50...
    LOOP_SLEEP = 0.01  # 每次请求时间间隔(秒), 可设置 0.1, 0.01, 0.001, 0.0001
    ERROR_NUM = 0  # 出错数

    obj = PressTet(press_url=press_url, data_json=data_sample)
    obj.run()
    
"""
===============压测结果===================
URL: http://localhost:8832/calculate
任务数量: 50 * 10 = 500
总耗时(秒): 21.13799810409546
每次请求耗时(秒): 0.04227599620819092
每秒承载请求数: 23.654084816249732
错误数量: 400

tcp 默认有心跳确保长连接,http1.1 无限制,nginx 默认一分半,gunicorn 默认 1 分钟,uvicorn 和 starlette 默认无限制,
所以要确保连接需要修改 nginx 和 gunicorn 的默认设置,另外浏览器也有限制但是比较长。

"""

reference

Guess you like

Origin blog.csdn.net/rensihui/article/details/129267528