Каркас бутылочной паутины — как остановить?

При запуске бутылочного веб-сервера без потока или подпроцесса проблем нет. Чтобы выйти из приложения бутылки -> CTRL + c.

В треде, как я могу программно остановить веб-сервер бутылки?

Я не нашел в документации метода stop() или чего-то подобного. Есть ли причина?


person Sandro Munda    schedule 01.07.2012    source источник
comment
@ThiefMaster (я не могу опубликовать ваш удаленный ответ, поэтому пишу здесь): почему вы его удалили? Является ли sys.exit(0) плохим решением? Если да, то почему? Я пытался, и это действительно не работает, но было бы интересно получить ваш ответ, который объясняет, почему это не работает :)   -  person Basj    schedule 05.05.2020


Ответы (11)


Для сервера по умолчанию (WSGIRef) я делаю следующее (на самом деле это более чистый подход, предложенный Викрамом Пуди):

from bottle import Bottle, ServerAdapter

class MyWSGIRefServer(ServerAdapter):
    server = None

    def run(self, handler):
        from wsgiref.simple_server import make_server, WSGIRequestHandler
        if self.quiet:
            class QuietHandler(WSGIRequestHandler):
                def log_request(*args, **kw): pass
            self.options['handler_class'] = QuietHandler
        self.server = make_server(self.host, self.port, handler, **self.options)
        self.server.serve_forever()

    def stop(self):
        # self.server.server_close() <--- alternative but causes bad fd exception
        self.server.shutdown()

app = Bottle()

@app.route('/')
def index():
    return 'Hello world'

@app.route('/stop')  # not working from here, it has to come from another thread
def stopit():
    server.stop()  

server = MyWSGIRefServer(port=80)
try:
    app.run(server=server)
except:
    print('Bye')

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

server.stop()
person mike    schedule 17.04.2013
comment
У меня возникли проблемы с использованием server_close() в методе остановки, указывающего закрытые дескрипторы файлов. вместо этого помогло использование self.server.shutdown(). - person rocksportrocker; 05.09.2013
comment
@rocksportrocker Вы правы, спасибо, что указали на это. Я обновил код. - person mike; 05.09.2013
comment
Используя этот метод, я получил ошибку «порт уже используется» при попытке снова запустить поток. Использование self.server.server_close() после строки self.server.shutdown() решило эту проблему для меня. - person Hassan; 15.11.2018
comment
Попробовав МНОГИЕ варианты, это единственный, который сработал для меня. В моем интерфейсе Qt я могу открыть сервер, закрыть его и снова открыть без каких-либо проблем. Хотя использование shutdown() у меня не сработало, мне пришлось использовать server_close() +1! - person Green Cell; 09.01.2019
comment
server.stop() Из другой темы действительно важно. Если это один и тот же поток, он не работает (см. маршрут /stop в моем редактировании). - person Basj; 05.05.2020

У меня были проблемы с закрытием сервера бутылок из запроса, так как бутылка, похоже, запускает запросы в подпроцессах.

В конце концов я нашел решение:

sys.stderr.close()

внутри запроса (который был передан серверу бутылок и удален).

person Cameron Blackwood    schedule 15.07.2012
comment
Кто-нибудь может сказать, есть ли проблемы с этим ответом? Почему это не самый популярный ответ? Это однострочное решение и, кажется, работает отлично. - person xilopaint; 22.01.2019

Обновленная версия ответа Майка.

from bottlepy.bottle import WSGIRefServer, run
from threading import Thread
import time

class MyServer(WSGIRefServer):
    def run(self, app): # pragma: no cover
        from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
        from wsgiref.simple_server import make_server
        import socket

        class FixedHandler(WSGIRequestHandler):
            def address_string(self): # Prevent reverse DNS lookups please.
                return self.client_address[0]
            def log_request(*args, **kw):
                if not self.quiet:
                    return WSGIRequestHandler.log_request(*args, **kw)

        handler_cls = self.options.get('handler_class', FixedHandler)
        server_cls  = self.options.get('server_class', WSGIServer)

        if ':' in self.host: # Fix wsgiref for IPv6 addresses.
            if getattr(server_cls, 'address_family') == socket.AF_INET:
                class server_cls(server_cls):
                    address_family = socket.AF_INET6

        srv = make_server(self.host, self.port, app, server_cls, handler_cls)
        self.srv = srv ### THIS IS THE ONLY CHANGE TO THE ORIGINAL CLASS METHOD!
        srv.serve_forever()

    def shutdown(self): ### ADD SHUTDOWN METHOD.
        self.srv.shutdown()
        # self.server.server_close()

def begin():
    run(server=server)

server = MyServer(host="localhost", port=8088)
Thread(target=begin).start()
time.sleep(2) # Shut down server after 2 seconds
server.shutdown()

Класс WSGIRefServer полностью скопирован с добавлением только 1 строки в метод run(). Также добавьте простой метод shutdown(). К сожалению, это необходимо из-за того, как бутылка создает метод run().

person Sepero    schedule 03.11.2013
comment
Попробовал это в своем собственном проекте, и он все закрывает, но я также хотел перезапустить его позже... чтобы перезагрузить некоторые вещи. Но когда я это делаю, кажется, что сервер возвращается в сеть, но не отвечает. Какие-нибудь мысли? - person Adam Haile; 16.09.2015

Вы можете сделать свой поток демоном, установив для свойства демона значение True перед вызовом start.

mythread = threading.Thread()
mythread.daemon = True
mythread.start()

Поток демона останавливается всякий раз, когда основной поток, в котором он работает, уничтожается или умирает. Единственная проблема заключается в том, что вы не сможете заставить поток выполнять какой-либо код при выходе, и если поток находится в процессе выполнения чего-либо, он будет немедленно остановлен, не имея возможности завершить выполняемый метод.

В Python нет способа явно остановить поток. Если вы хотите иметь больший контроль над возможностью остановки вашего сервера, вам следует изучить Python Процессы из модуля multiprocesses.

person enrybo    schedule 20.08.2012

Поскольку в бутылке нет механизма, для нее требуется взлом. Это, пожалуй, самый чистый вариант, если вы используете сервер WSGI по умолчанию:

В коде бутылки сервер WSGI запускается с:

srv.serve_forever()

Если вы запустили бутылку в своем собственном потоке, вы можете остановить ее, используя:

srv.shutdown()

Чтобы получить доступ к переменной srv в вашем коде, вам нужно отредактировать исходный код бутылки и сделать его глобальным. После изменения кода бутылки это будет выглядеть так:

srv = None #make srv global
class WSGIRefServer(ServerAdapter):
    def run(self, handler): # pragma: no cover
        global srv #make srv global
        ...
person Vikram Pudi    schedule 22.02.2013
comment
Чтобы получить доступ к переменной srv в вашем коде, вам нужно отредактировать исходный код бутылки и сделать его глобальным. Действительно? оо - person Sandro Munda; 22.02.2013
comment
Поскольку переменная srv определена внутри метода класса, она недоступна извне. - person Vikram Pudi; 22.02.2013

Вот один из вариантов: предоставить собственный сервер (такой же, как по умолчанию), который записывает сам себя:

import bottle


class WSGI(bottle.WSGIRefServer):
    instances = []

    def run(self, *args, **kw):
        self.instances.append(self)
        super(WSGI, self).run(*args, **kw)

# some other thread:
bottle.run(host=ip_address, port=12345, server=WSGI)

# control thread:
logging.warn("servers are %s", WSGI.instances)
person Dima Tisnek    schedule 20.11.2015
comment
ОК, это только начало, похоже, все еще необходимо раскопать лежащий в основе srv. - person Dima Tisnek; 20.11.2015

Я предполагаю, что веб-сервер бутылки работает вечно, пока не завершится. Нет таких методов, как stop().

Но вы можете сделать что-то вроде этого:

from bottle import route, run
import threading, time, os, signal, sys, operator

class MyThread(threading.Thread):
    def __init__(self, target, *args):
        threading.Thread.__init__(self, target=target, args=args)
        self.start()

class Watcher:
    def __init__(self):
        self.child = os.fork()
        if self.child == 0:
            return
        else:
            self.watch()

    def watch(self):
        try:
            os.wait()
        except KeyboardInterrupt:
            print 'KeyBoardInterrupt'
            self.kill()
        sys.exit()

    def kill(self):
        try:
            os.kill(self.child, signal.SIGKILL)
        except OSError: pass

def background_process():
    while 1:
        print('background thread running')
        time.sleep(1)

@route('/hello/:name')
def index(name='World'):
    return '<b>Hello %s!</b>' % name

def main():
    Watcher()
    MyThread(background_process)

    run(host='localhost', port=8080)

if __name__ == "__main__":
    main()

Затем вы можете использовать Watcher.kill(), когда вам нужно убить свой сервер.

Вот код run() функции бутылки:

попробуйте: приложение = приложение или default_app(), если isinstance (приложение, базовая строка): приложение = load_app (приложение), если не вызывается (приложение): поднять ValueError («Приложение не вызывается: %r» % приложение)

    for plugin in plugins or []:
        app.install(plugin)

    if server in server_names:
        server = server_names.get(server)
    if isinstance(server, basestring):
        server = load(server)
    if isinstance(server, type):
        server = server(host=host, port=port, **kargs)
    if not isinstance(server, ServerAdapter):
        raise ValueError("Unknown or unsupported server: %r" % server)

    server.quiet = server.quiet or quiet
    if not server.quiet:
        stderr("Bottle server starting up (using %s)...\n" % repr(server))
        stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
        stderr("Hit Ctrl-C to quit.\n\n")

    if reloader:
        lockfile = os.environ.get('BOTTLE_LOCKFILE')
        bgcheck = FileCheckerThread(lockfile, interval)
        with bgcheck:
            server.run(app)
        if bgcheck.status == 'reload':
            sys.exit(3)
    else:
        server.run(app)
except KeyboardInterrupt:
    pass
except (SyntaxError, ImportError):
    if not reloader: raise
    if not getattr(server, 'quiet', False): print_exc()
    sys.exit(3)
finally:
    if not getattr(server, 'quiet', False): stderr('Shutdown...\n')

Как видите, другого выхода из цикла run нет, кроме некоторых исключений. Функция server.run зависит от используемого вами сервера, но универсального quit-метода все равно не существует.

person Igor Chubin    schedule 01.07.2012
comment
Я хотел бы избежать использования подпроцесса. Является ли это возможным ? Спасибо за Ваш ответ. - person Sandro Munda; 01.07.2012
comment
Это не подпроцесс, это поток; но я тебя понимаю. Я не уверен, что это возможно. Я полагаю, что нет. - person Igor Chubin; 01.07.2012
comment
Когда вы делаете self.child = os.fork(), вы разветвляете процесс. нет ? Хорошо, может быть, есть объяснение отсутствия метода stop(). Мне любопытно :) - person Sandro Munda; 01.07.2012

Преимущество этого столь же неуклюжего хака заключается в том, что вам не нужно копировать и вставлять какой-либо код из Bottle.py:

# The global server instance.                                                                                             
server = None

def setup_monkey_patch_for_server_shutdown():
    """Setup globals to steal access to the server reference.                                                             
    This is required to initiate shutdown, unfortunately.                                                                 
    (Bottle could easily remedy that.)"""

    # Save the original function.                                                                                         
    from wsgiref.simple_server import make_server

    # Create a decorator that will save the server upon start.                                                            
    def stealing_make_server(*args, **kw):
        global server
        server = make_server(*args, **kw)
        return server

    # Patch up wsgiref itself with the decorated function.                                                                
    import wsgiref.simple_server
    wsgiref.simple_server.make_server = stealing_make_server

setup_monkey_patch_for_server_shutdown()

def shutdown():
    """Request for the server to shutdown."""
    server.shutdown()
person blais    schedule 02.03.2014

Это точно такой же метод, как sepero и < ответ href="https://stackoverflow.com/questions/11282218/bottle-web-framework-how-to-stop/16056443#16056443">mike, но теперь намного проще с версией Bottle 0.13 +:

from bottle import W, run, route
from threading import Thread
import time

@route('/')
def index():
    return 'Hello world'

def shutdown():
    time.sleep(5)
    server.srv.shutdown()

server = WSGIRefServer(port=80)
Thread(target=shutdown).start()
run(server=server)

Также по теме: https://github.com/bottlepy/bottle/issues/1229 и https://github.com/bottlepy/bottle/issues/1230.


Другой пример с маршрутом http://localhost/stop для завершения работы:

from bottle import WSGIRefServer, run, route
from threading import Thread

@route('/')
def index():
    return 'Hello world'

@route('/stop')
def stopit():
    Thread(target=shutdown).start()

def shutdown():
    server.srv.shutdown()

server = WSGIRefServer(port=80)
run(server=server)

PS: требуется как минимум Bottle 0.13dev.

person Basj    schedule 05.05.2020
comment
Атрибут srv присутствует в bottle.WSGIRefServer уже несколько лет назад, я не понимаю, почему до сих пор не вышла Bottle 0.13! - person BeardOverflow; 20.10.2020

Я нашел это решение самым простым, но для него требуется установить пакет «psutil», чтобы получить текущий процесс. Также требуется модуль «сигналы», но он является частью стандартной библиотеки.

@route('/shutdown')
def shutdown():
    current_process = psutil.Process()
    current_process.send_signal(signal.CTRL_C_EVENT)
    return 'Shutting down the web server'

Надеюсь, это кому-то пригодится!

person Adrian Mc    schedule 27.03.2020

Этот вопрос был первым в моем поиске Google, поэтому я опубликую свой ответ:

Когда сервер запускается с классом Bottle(), у него есть метод close() для остановки сервера. Из исходного кода:

""" Закройте приложение и все установленные плагины. """

Например:

class Server:
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._app = Bottle()
    def stop(self):
        # close ws server
        self._app.close()
    def foo(self):
        # More methods, routes...

Вызов метода остановки остановит сервер.

person Slye    schedule 13.12.2018
comment
Интересное решение @Slye! Не могли бы вы опубликовать полный пример (с import bottle, run(...) и т. д.), чтобы сделать ваш код воспроизводимым? Здесь мы действительно не знаем, как использовать ваш код... Заранее спасибо! - person Basj; 05.05.2020
comment
Я пробовал app = Bottle() и т. д., но затем выполнение app.close() не останавливало сервер. - person Basj; 05.05.2020