Как я могу запустить два сервера фляг в двух потоках/процессах на двух портах в рамках одной программы Python?

Я пишу библиотеку отладки python, которая открывает фляжный сервер в новом потоке и предоставляет информацию о программе, в которой он работает. Это отлично работает, когда отлаживаемая программа сама по себе не является веб-сервером. Однако, если я попытаюсь запустить его одновременно с другим сервером фляг, работающим в режиме отладки, все сломается. Когда я пытаюсь получить доступ ко второму серверу, результат чередуется между двумя серверами.

Вот пример:

from flask.app import Flask
from threading import Thread

# app1 represents my debugging library

app1 = Flask('app1')

@app1.route('/')
def foo():
    return '1'

Thread(target=lambda: app1.run(port=5001)).start()

# Cannot change code after here as I'm not the one writing it

app2 = Flask('app2')

@app2.route('/')
def bar():
    return '2'

app2.run(debug=True, port=5002)

Теперь, когда я посещаю http://localhost:5002/ в своем браузере, результат может быть либо 1, либо 2 вместо этого. постоянно быть 2.

Использование multiprocessing.Process вместо Thread дает тот же результат.

Как это происходит и как этого избежать? Это неизбежно с flask/werkzeug/WSGI? Мне нравится flask за его простоту, и в идеале я хотел бы продолжать его использовать. Если это невозможно, какую самую простую библиотеку/фреймворк я могу использовать, чтобы не мешать другим веб-серверам, работающим одновременно? Я также хотел бы использовать потоки вместо процессов, если это возможно.


person Alex Hall    schedule 11.05.2017    source источник


Ответы (1)


Перезагрузчик werkzeug (который по умолчанию используется в режиме отладки) создает новый процесс с использованием subprocess.call, упрощенно он делает что-то вроде:

new_environ = os.environ.copy()
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
subprocess.call([sys.executable] + sys.argv, env=new_environ, close_fds=False)

Это означает, что ваш скрипт выполняется повторно, что обычно нормально, если все, что он содержит, представляет собой app.run(), но в вашем случае он перезапустит и app1, и app2, но теперь оба используют один и тот же порт, потому что, если ОС поддерживает это, порт прослушивания открыт. в родительском процессе, наследуется дочерним и используется там напрямую, если установлена ​​переменная среды WERKZEUG_SERVER_FD.

Итак, теперь у вас есть два разных приложения, так или иначе использующих один и тот же сокет.

Вы можете увидеть это лучше, если добавите некоторый вывод, например:

from flask.app import Flask
from threading import Thread
import os

app1 = Flask('app1')

@app1.route('/')
def foo():
    return '1'

def start_app1():
    print("starting app1")
    app1.run(port=5001)

app2 = Flask('app2')

@app2.route('/')
def bar():
    return '2'

def start_app2():
    print("starting app2")
    app2.run(port=5002, debug=True)

if __name__ == '__main__':
    print("PID:", os.getpid())
    print("Werkzeug subprocess:", os.environ.get("WERKZEUG_RUN_MAIN"))
    print("Inherited FD:", os.environ.get("WERKZEUG_SERVER_FD"))
    Thread(target=start_app1).start()
    start_app2()

Это печатает, например:

PID: 18860
Werkzeug subprocess: None
Inherited FD: None
starting app1
starting app2
 * Running on http://127.0.0.1:5001/ (Press CTRL+C to quit)
 * Running on http://127.0.0.1:5002/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
PID: 18864
Werkzeug subprocess: true
Inherited FD: 4
starting app1
starting app2
 * Debugger is active!

Если вы измените код запуска на

if __name__ == '__main__':
    if os.environ.get("WERKZEUG_RUN_MAIN")) != 'true':
        Thread(target=start_app1).start()
    start_app2()

тогда должно работать корректно, релоадером перезагружается только app2. Однако он выполняется в отдельном процессе, а не в другом потоке, что подразумевается при использовании режима отладки.

Чтобы избежать этого, можно было бы использовать:

if __name__ == '__main__':
    os.environ["WERKZEUG_RUN_MAIN"] = 'true'
    Thread(target=start_app1).start()
    start_app2()

Теперь релоадер считает, что он уже запущен в подпроцессе и не запускает новый, все выполняется в том же процессе. Перезагрузка не сработает, и я не знаю, какие еще побочные эффекты могут быть.

person mata    schedule 12.05.2017