出錯現象: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