Bottle web 框架 - 如何停止?

Posted

技术标签:

【中文标题】Bottle web 框架 - 如何停止?【英文标题】:Bottle web framework - How to stop? 【发布时间】:2012-07-02 04:35:20 【问题描述】:

在没有线程或子进程的情况下启动瓶子网络服务器时,没有问题。要退出瓶子应用程序 -> CTRL + c

在一个线程中,我如何以编程方式停止瓶子网络服务器?

我没有在文档中找到stop() 方法或类似的东西。有什么原因吗?

【问题讨论】:

@ThiefMaster(我无法发布您已删除的答案,所以我在这里发布):您为什么要删除它? sys.exit(0) 是一个糟糕的解决方案吗?如果是这样,为什么?我试过了,确实它不起作用,但是让你的答案解释为什么它不起作用会很有趣:) 【参考方案1】:

对于默认(WSGIRef)服务器,我就是这样做的(实际上这是 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')

当我想从另一个线程停止瓶子应用程序时,我执行以下操作:

server.stop()

【讨论】:

我在停止方法中使用“server_close()”时遇到问题,表明文件句柄已关闭。使用“self.server.shutdown()”反而有帮助。 @rocksportrocker 你说得对,感谢您指出这一点。我更新了代码。 使用此方法时,尝试再次运行线程时出现“端口已在使用”错误。在self.server.shutdown() 行之后使用self.server.server_close() 为我解决了这个问题。 在尝试了许多选项之后,这是唯一对我有用的选项。在我的Qt 界面中,我可以打开服务器,关闭它,然后重新打开它而不会出现任何问题。虽然使用shutdown() 对我不起作用,但我不得不使用server_close() +1! server.stop() “来自另一个线程” 非常重要。如果它是同一个线程它不起作用(请参阅我的编辑中的路线/stop)。【参考方案2】:

我无法从请求中关闭瓶服务器,因为瓶似乎在子进程中运行请求。

我最终找到了解决办法:

sys.stderr.close()

在请求内部(被传递到瓶子服务器并取消它)。

【讨论】:

有人能说这个答案有没有问题吗?为什么这不是更受欢迎的答案?这是一种单线解决方案,似乎效果很好。【参考方案3】:

迈克答案的更新版本。

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() 方法的方式。

【讨论】:

在我自己的项目中试过这个,它关闭了一切,但我也想在之后重新启动它......重新加载一些东西。但是当我这样做时,服务器似乎重新联机,但没有响应。有什么想法吗?【参考方案4】:

您可以通过在调用 start 之前将 daemon 属性设置为 True 来使您的线程成为守护进程。

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

每当它运行的主线程被杀死或死亡时,一个守护线程就会停止。唯一的问题是你将无法让线程在退出时运行任何代码,如果线程正在执行某项操作,它将立即停止,而无法完成它正在运行的方法。

在 Python 中没有办法真正显式地停止线程。如果您想更好地控制停止服务器,您应该从 multiprocesses 模块中查看 Python Processes。

【讨论】:

【参考方案5】:

由于瓶子不提供机制,因此需要破解。如果您使用默认的 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
        ...

【讨论】:

"要访问代码中的 srv 变量,您需要编辑瓶子源代码并使其成为全局变量。"真的吗? oO 由于 srv 变量是在类的方法内部定义的,因此无法从外部访问。【参考方案6】:

这里有一个选项:提供自定义服务器(与默认相同),记录自己:

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)

【讨论】:

好吧,这只是一个开始,看起来仍然需要挖掘底层的 srv。【参考方案7】:

我想瓶子网络服务器会一直运行直到它终止。没有像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()函数代码:

尝试: app = app 或 default_app() 如果是实例(应用程序,基本字符串): 应用程序 = 加载应用程序(应用程序) 如果不可调用(应用程序): raise ValueError("Application is not callable: %r" % app)

    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 方法。

【讨论】:

我想避免使用子进程。可能吗 ?谢谢你的回答。 不是子进程,是线程;但我理解你。我不确定这是否可能。我想不会。 当你执行“self.child = os.fork()”时,你会分叉一个进程。不 ?好的,也许有一个关于没有 stop() 方法的解释。我很好奇:)【参考方案8】:

这个同样笨拙的 hack 的优点是不需要从 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()

【讨论】:

【参考方案9】:

我发现这个解决方案是最简单的,但它确实需要安装“psutil”包才能获取当前进程。它还需要“信号”模块,但这是标准库的一部分。

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

希望对某人有用!

【讨论】:

【参考方案10】:

这与sepero's 和mike's answer的方法完全相同,但现在使用 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。

【讨论】:

srv 属性从几年前就出现在bottle.WSGIRefServer 中,我不明白为什么Bottle 0.13 还没有发布!【参考方案11】:

Bottle 服务器的控制台日志告诉我们,官方关闭服务器的方式是“Hit Ctrl-C”:

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

为什么不简单地以编程方式遵循它?

点击“Ctrl-C”只不过是向进程发送 SIGINT,我们可以通过内置模块来实现:

    使用 os.getpid() 获取当前 PID。 使用 os.kill() 终止进程。请记住传递 SIGINT,因此它与“Hit Ctrl-C”完全相同。 将“kill”封装在另一个线程中并在几秒钟后启动它,这样客户端就不会出错。

这是服务器代码:

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)

这是客户端代码:

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

请注意,在函数“handle_stop_request”中,我们并没有立即停止服务器,而是启动了一个线程然后返回空字符串。通过这种机制,当客户端请求“http://127.0.0.1:8080/stop”时,可以正常得到响应(空字符串)。之后,服务器将关闭。如果我们在“handle_stop_request”函数中关闭服务器,服务器将在返回客户端之前关闭连接,因此客户端将收到“ConnectionError”。

服务器端输出:

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

客户端输出:

Hello

代码在 Python 3.7 和 Bottle 0.12 下测试。

【讨论】:

这个答案的简单性和横向思考给我留下了深刻的印象。我没有使用 WSGI 服务器,所以这非常适合我的用例。【参考方案12】:

这个问题在我的谷歌搜索中排名第一,所以我会发布我的答案:

当使用 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...

调用 stop 方法将停止服务器。

【讨论】:

有趣的解决方案@Slye!您能否发布一个完整的示例(使用import bottlerun(...) 等)以使您的代码可重现?在这里,我们真的不知道如何使用您的代码...提前致谢! 我尝试了app = Bottle() 等,但随后执行app.close() 并没有停止服务器。

以上是关于Bottle web 框架 - 如何停止?的主要内容,如果未能解决你的问题,请参考以下文章

微型 Python Web 框架: Bottle

python web 开发框架之Bottle

基于Bottle 封装一下自用web前后端分离开发框架(1):项目规划

bottle框架

Python轻量级Web框架:Bottle库!

Python轻量级Web框架:Bottle库!