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

出错现象:python

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

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运行的话,是没有问题的,外网能够正常访问.shell

因此问题就出在gunicorn 和 flask 的 wsgi 对接上.数据库

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

我其余方面想了不少,试了不少,都没解决, 而后盯着这句话想了一二十分钟........ 嗯.......多是这个缘由:服务器

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

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

这就出来个想法了.dom

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

个人启动文件代码大概以下:

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的启动命令改为:

1 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

相关文章
相关标签/搜索