记一次 gunicorn 启动 flask 出问题的经历

出错现象:

gunicorn+nginx+flask 部署项目, 部署过程没问题,项目也正常启动了,但是一旦访问接口,就会报错:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/gunicorn/workers/sync.py", line 135, in handle
    self.handle_request(listener, req, client, addr)
  File "/usr/local/lib/python3.6/dist-packages/gunicorn/workers/sync.py", line 176, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
TypeError: __call__() takes from 1 to 2 positional arguments but 3 were given

但是我通过 runserver运行的话,是没有问题的,外网可以正常访问.

所以问题就出在gunicorn 和 flask 的 wsgi 对接上.

gunicorn 启动时的方式是 gunicorn [options] file:app

我其他方面想了很多,试了很多,都没解决, 然后盯着这句话想了一二十分钟… 嗯…可能是这个原因:

直接通过终端 runserver方式启动的话,会直接运行启动文件, 最终提供服务的是app (就是flask的实例)

我为了数据库迁移, 启动文件注册了flask-script的对象,最后启动的实际是flask-script对象, 这个对象提供的就是些命令行之类的东西, 简单地说,就是一个命令行扩展, 在不使用命令行迁移数据库的时候没什么用(至少我小白看来是这样).

这就出来个想法了.

gunicorn说白了就是服务代理, 用来处理高并发的, 通过多进程/协程/线程 的方式提供并发访问, 所以gunicorn 代理的是flask对象, 也就是flask实例化的APP.

我的启动文件代码大概如下:

from projects import APP, db
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager

manage = Manager(APP)
migrate = Migrate(app=APP, db=db)
manage.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manage.run()

我这次的错误就在于将 flask-script的实例提供给了gunicorn, 这就导致了gunicorn接收到的参数并不是标准wsgi参数,所以报错.

解决办法很简单: 将APP(对于此处而言)交给gunicorn代理就好了.

所以gunicorn的启动命令改成:

gunicorn -D -w 3 -t 300 -b 0.0.0.0:5000 manage:APP

有需要的话,加上日志的配置,个人建议最好加上日志,日志是处理问题的最直接资料

gunicorn -D --error-logfile=/logs/gunicorn.log --pid=/logs/gunicorn.pid --access-logfile=/logs/access.log -w 3 -t 300 -b 0.0.0.0:5000 manage:APP

所以本次问题的原因在于wsgi协议未被标准的执行,代理服务器代理的是服务器APP, 而我一直没注意到这个.

下面是flask-script在终端直接runserver时的运行机制:

如我上方代码所示,manage是flask-script的实例, 执行Python manage时, 会执行该实例的run()方法

    def run(self, commands=None, default_command=None):
        """
        Prepares manager to receive command line input. Usually run
        inside "if __name__ == "__main__" block in a Python script.

        :param commands: optional dict of commands. Appended to any commands
                         added using add_command().

        :param default_command: name of default command to run if no
                                arguments passed.
        """

        if commands:
            self._commands.update(commands)

        # Make sure all of this is Unicode
        argv = list(text_type(arg) for arg in sys.argv)
        if default_command is not None and len(argv) == 1:
            argv.append(default_command)

        try:
            result = self.handle(argv[0], argv[1:])
        except SystemExit as e:
            result = e.code

        sys.exit(result or 0)
如上方法会执行 handle() 然后通过一系列的方法,路过执行如下代码:

    def add_default_commands(self):
        """
        Adds the shell and runserver default commands. To override these,
        simply add your own equivalents using add_command or decorators.
        """

        if "shell" not in self._commands:
            self.add_command("shell", Shell())
        if "runserver" not in self._commands:
            self.add_command("runserver", Server())
上面的代码会给flask-script对象添加两个命令,其中就有我们很熟悉的 runserver, 该命令会执行Server()对象的call方法,call方法如下:

    def __call__(self, app, host, port, use_debugger, use_reloader,
                 threaded, processes, passthrough_errors, ssl_crt, ssl_key):
        # we don't need to run the server in request context
        # so just run it directly

        if use_debugger is None:
            use_debugger = app.debug
            if use_debugger is None:
                use_debugger = True
                if sys.stderr.isatty():
                    print("Debugging is on. DANGER: Do not allow random users to connect to this server.", file=sys.stderr)
        if use_reloader is None:
            use_reloader = use_debugger

        if None in [ssl_crt, ssl_key]:
            ssl_context = None
        else:
            ssl_context = (ssl_crt, ssl_key)

        app.run(host=host,
                port=port,
                debug=use_debugger,
                use_debugger=use_debugger,
                use_reloader=use_reloader,
                threaded=threaded,
                processes=processes,
                passthrough_errors=passthrough_errors,
                ssl_context=ssl_context,
                **self.server_options)

到这里就很明了了,在执行 python manage.py runserver 的时候,如果命令不是flask-script的提供的其他命令的话,就会执行flask实例的run方法, 实质上,就是 Flask(name).run()

而flask-script就是监测有没有收到自己的命令.

虽然flask-script也会代理flask的APP, 但是flask-script的对象并不等同与flask的实例,所以提供给gunicorn的还必须得是flask的app

转载于:https://www.cnblogs.com/haiton/p/11295495.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在安装了 GunicornFlask 之后,你可以使用 Gunicorn启动 Flask 应用。 示例命令如下: ``` gunicorn --bind 0.0.0.0:8000 --workers 4 myapp:app ``` 其中,`myapp` 是你的 Flask 应用所在的模块,`app` 是你的 Flask 应用实例。 `--bind` 参数用于指定 Gunicorn 应监听的地址和端口。 `--workers` 参数用于指定 Gunicorn 应使用的工作进程数量。 你也可以使用其他的命令行参数来调整 Gunicorn 的行为,例如 `--timeout` 参数用于设置超时时间,`--log-level` 参数用于设置日志录的级别。 更多的命令行参数的信息,可以在 Gunicorn 文档中找到。 ### 回答2: 使用Gunicorn启动Flask应用的示例命令如下: ``` gunicorn -w 4 -b 0.0.0.0:8000 app:app ``` 这个命令中,`gunicorn` 是 Gunicorn 的命令行工具,`-w` 用于指定工作进程的数量,这里是4个工作进程。`-b` 用于指定绑定的IP地址和端口号,这里是绑定在0.0.0.0的8000端口上。`app:app` 表示启动的主应用文件(app.py)中的 Flask 应用。请注意,如果你的主应用文件名不是 app.py,那么需要将这个参数修改为相应的文件名。 执行这个命令后,Gunicorn启动 Flask 应用,并监听在指定的地址和端口。你可以根据需要在后面添加其他参数,以满足你的应用需求。启动成功后,你可以在浏览器中访问 http://localhost:8000 来查看你的 Flask 应用。 ### 回答3: 使用Gunicorn启动Flask应用的示例命令如下: 假设Flask应用的文件名为app.py,应用的主要逻辑在名为app的Flask实例中。 首先,确保已经安装了Gunicorn。可以使用以下命令安装: ```shell pip install gunicorn ``` 接下来,在终端中切换到Flask应用所在的目录,然后运行以下命令启动Flask应用: ```shell gunicorn app:app ``` 在上述命令中,第一个app表示要运行的Python模块名(即Flask应用所在的Python文件名,不包括.py扩展名);第二个app表示Flask应用的实例名。 默认情况下,Gunicorn将在本地主机的8000端口上启动Flask应用。可以通过添加`-b`选项指定要使用的IP地址和端口。例如,要在IP地址为127.0.0.1、端口为5000的位置启动Flask应用,可以运行以下命令: ```shell gunicorn -b 127.0.0.1:5000 app:app ``` 运行上述命令后,Gunicorn启动Flask应用,并在终端显示相关日志。此时,可以通过将浏览器访问http://127.0.0.1:5000来验证Flask应用是否正常运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值