Bottle web framework - ¿Cómo parar?

Al iniciar un servidor web de botella sin un hilo o un subproceso, no hay problema. Para salir de la aplicación de la botella -> CTRL + c.

En un hilo, ¿cómo puedo programáticamente detener el servidor web de la botella?

No encontré un stop() método o algo así en la documentación. Hay una razón ?

preguntado el 01 de julio de 12 a las 14:07

@ThiefMaster (no puedo publicar en su respuesta eliminada, así que publico aquí): ¿por qué la eliminó? Es sys.exit(0) una mala solución? Si es así, ¿por qué? Lo intenté y de hecho no funciona, pero teniendo tu respuesta que explica el porqué no funciona seria interesante :) -

12 Respuestas

Para el servidor predeterminado (WSGIRef), esto es lo que hago (en realidad, es un enfoque más limpio de la sugerencia de Vikram Pudi):

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')

Cuando quiero detener la aplicación de la botella, desde otro hilo, hago lo siguiente:

server.stop()

contestado el 05 de mayo de 20 a las 12:05

Tuve problemas al usar "server_close()" en el método de parada, lo que indica identificadores de archivos cerrados. usar "self.server.shutdown()" en su lugar ayudó. - rockportrocker

@rocksportrocker Tienes razón, gracias por señalar esto. Actualicé el código. - micro

Al usar este método, obtuve el error "Puerto ya en uso" al intentar ejecutar el hilo nuevamente. Utilizando self.server.server_close() después de la línea self.server.shutdown() resolvió este problema para mí. - Hassan

Después de probar MUCHAS opciones esta es la única que me ha funcionado. En mi Qt interfaz puedo abrir un servidor, cerrarlo y volver a abrirlo sin contratiempos. Aunque usando shutdown() no funcionó para mí, tuve que usar server_close() +1! - Celda verde

server.stop() "De otro hilo" es realmente importante Si es el mismo hilo no sirve (ver ruta /stop en mi edición). - bajo

Tuve problemas para cerrar un servidor de botella desde dentro de una solicitud, ya que la botella parece ejecutar solicitudes en subprocesos.

Finalmente encontré que la solución era hacer:

sys.stderr.close()

dentro de la solicitud (que se pasó al servidor de la botella y la eliminó).

Respondido 15 Jul 12, 05:07

¿Alguien puede decir si hay algún problema con esta respuesta? ¿Por qué esta no es la respuesta más votada? Es una solución de una sola línea y parece funcionar perfectamente. - xilopintura

Una versión actualizada de la respuesta de Mike.

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()

La clase WSGIRefServer se copia por completo con solo 1 línea agregada al método run(). También agregue un método simple de apagado (). Desafortunadamente, esto es necesario debido a la forma en que bottle crea el método run().

respondido 03 nov., 13:04

Intenté esto en mi propio proyecto y se apaga todo, pero también quería reiniciarlo después... para recargar algunas cosas. Pero cuando lo hago, el servidor parece volver a estar en línea, pero no responde. ¿Alguna idea? - adam haile

Puede convertir su subproceso en un demonio configurando la propiedad del demonio en True antes de llamar a start.

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

Un subproceso demoníaco se detendrá cada vez que el subproceso principal en el que se está ejecutando se elimine o muera. El único problema es que no podrá hacer que el subproceso ejecute ningún código al salir y si el subproceso está en proceso de hacer algo, se detendrá inmediatamente sin poder finalizar el método que se está ejecutando.

No hay forma en Python de detener explícitamente un hilo. Si desea tener más control sobre la posibilidad de detener su servidor, debe consultar Python Procesos from the multiprocesos módulo.

Respondido el 16 de enero de 13 a las 20:01

Dado que la botella no proporciona un mecanismo, requiere un truco. Este es quizás el más limpio si está utilizando el servidor WSGI predeterminado:

En el código de la botella, el servidor WSGI se inicia con:

srv.serve_forever()

Si ha iniciado la botella en su propio hilo, puede detenerlo usando:

srv.shutdown()

Para acceder a la variable srv en su código, debe editar el código fuente de la botella y hacerlo global. Después de cambiar el código de la botella, se vería así:

srv = None #make srv global
class WSGIRefServer(ServerAdapter):
    def run(self, handler): # pragma: no cover
        global srv #make srv global
        ...

Respondido 22 Feb 13, 06:02

"Para acceder a la variable srv en su código, debe editar el código fuente de la botella y hacerlo global". ¿En serio? oO- Sandro Munda

Dado que la variable srv se define dentro de un método de una clase, no se puede acceder a ella desde el exterior. - vikram pudi

Aquí hay una opción: proporcionar un servidor personalizado (igual que el predeterminado), que se registra solo:

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)

respondido 20 nov., 15:15

Bien, eso es solo un comienzo, parece que aún se necesita desenterrar el srv subyacente. - dima tisnek

Supongo que el servidor web de la botella se ejecuta para siempre hasta que finaliza. No hay métodos como stop().

Pero puedes hacer algo como esto:

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()

Entonces puedes usar Watcher.kill() cuando necesite matar su servidor.

Aquí está el código de run() Función de la botella:

pruebe: aplicación = aplicación o aplicación_predeterminada() si es una instancia (aplicación, cadena base): aplicación = carga_aplicación (aplicación) si no se puede llamar (aplicación): aumentar ValueError ("La aplicación no se puede llamar: %r" % aplicación)

    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')

Como puede ver, no hay otra forma de salir del run bucle, salvo algunas excepciones. los server.run función depende del servidor que utilice, pero no hay universal quit-método de todos modos.

Respondido 01 Jul 12, 16:07

Me gustaría evitar usar un subproceso. Es posible ? Gracias por tu respuesta. - Sandro Munda

No es un subproceso, es un hilo; pero te entiendo. No estoy seguro de que sea posible. Supongo que no. - Igor Chubin

Cuando haces "self.child = os.fork()", bifurcas un proceso. no ? Ok, tal vez haya una explicación sobre no tener un método stop(). Soy curioso :) - Sandro Munda

Este truco igualmente torpe tiene la ventaja de que no tiene que copiar y pegar ningún código de 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()

respondido 02 mar '14, 00:03

Descubrí que esta solución es la más fácil, pero requiere que el paquete "psutil" esté instalado para obtener el proceso actual. También requiere el módulo de "señales", pero eso es parte de la biblioteca estándar.

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

Espero que sea de utilidad para alguien!

respondido 27 mar '20, 14:03

Este es exactamente el mismo método que séperoy microLa respuesta de, pero ahora mucho más simple con la versión 0.13+ de Bottle:

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)

También relacionado: https://github.com/bottlepy/bottle/issues/1229 y https://github.com/bottlepy/bottle/issues/1230.


Otro ejemplo con una ruta. http://localhost/stop para hacer el apagado:

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)

PD: requiere al menos Bottle 0.13dev.

contestado el 05 de mayo de 20 a las 13:05

La srv atributo ha estado presente en bottle.WSGIRefServer ¡Desde hace varios años, no entiendo por qué Bottle 0.13 aún no se lanza! - barbadesbordamiento

El registro de la consola del servidor Bottle nos dice que la forma oficial de apagar el servidor es "Pulsar Ctrl-C":

Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

¿Por qué no simplemente seguirlo? programáticamente?

Presionar "Ctrl-C" no es más que enviar SIGINT al proceso, y podemos lograrlo con solo módulos integrados:

  1. Obtenga el PID actual con os.getpid().
  2. Mata el proceso con os.kill(). Recuerde pasar SIGINT para que sea exactamente igual que "Pulse Ctrl-C".
  3. Envuelva el 'matar' en otro hilo e inícielo después de unos segundos para que el cliente no reciba un error.

Aquí está el código del servidor:

from bottle import route, run
import os
import signal
from threading import Thread
import time


@route('/hello')
def return_hello():
    return 'Hello'


@route('/stop')
def handle_stop_request():
    # Handle "stop server" request from client: start a new thread to stop the server
    Thread(target=shutdown_server).start()
    return ''


def shutdown_server():
    time.sleep(2)
    pid = os.getpid()  # Get process ID of the current Python script
    os.kill(pid, signal.SIGINT)
    # Kill the current script process with SIGINT, which does same as "Ctrl-C"


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

Aquí está el código del cliente:

import requests


def request_service(service_key):
    url = f'http://127.0.0.1:8080/{service_key}'
    response = requests.get(url)
    content = response.content.decode('utf-8')
    print(content)


request_service('hello')
request_service('stop')

Tenga en cuenta que en la función "handle_stop_request" no detuvimos el servidor inmediatamente, sino que comenzamos un hilo y luego devolvimos una cadena vacía. Con este mecanismo, cuando un cliente solicita "http://127.0.0.1:8080/stop", puede obtener una respuesta (la cadena vacía) normalmente. Después de eso, el servidor se apagará. Si apagamos el servidor en la función "handle_stop_request", el servidor cerrará la conexión antes de volver al cliente y, por lo tanto, el cliente obtendrá "ConnectionError".

Salida del lado del servidor:

Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

127.0.0.1 - - [23/Nov/2021 11:18:08] "GET /hello HTTP/1.1" 200 5
127.0.0.1 - - [23/Nov/2021 11:18:08] "GET /stop HTTP/1.1" 200 0

Salida del lado del cliente:

Hello

El código fue probado bajo Python 3.7 y Bottle 0.12.

respondido 23 nov., 21:06

Estoy impresionado por la simplicidad y el pensamiento lateral de esta respuesta. No estoy usando un servidor WSGI, por lo que se ajusta perfectamente a mi caso de uso. - carrerayman

Esta pregunta fue la primera en mi búsqueda de Google, así que publicaré mi respuesta:

Cuando el servidor se inicia con la clase Bottle(), tiene un método close() para detener el servidor. Desde el código fuente:

""" Cierra la aplicación y todos los complementos instalados. """

Por ejemplo:

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...

Llamar al método de detención detendrá el servidor.

Respondido el 13 de diciembre de 18 a las 16:12

Solución interesante @Slye! ¿Podría publicar un ejemplo completo (con import bottle, run(...), etc.) para que su código sea reproducible? Aquí no sabemos muy bien cómo usar tu código... ¡Gracias de antemano! - bajo

Lo intenté app = Bottle() etc. pero luego haciendo app.close() no detiene el servidor. - bajo

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.