在没有线程或子进程的情况下启动瓶子网络服务器时,没有问题。退出瓶子应用程序 -> CTRL
+ c
。
在一个线程中,如何以编程方式停止瓶子网络服务器?
我在文档中没有找到stop()
方法或类似的东西。有原因吗?
对于默认(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()
我无法从请求中关闭瓶子服务器,因为瓶子似乎在子进程中运行请求。
我最终发现解决方案是:
sys.stderr.close()
在请求内部(被传递到瓶子服务器并取消它)。
迈克答案的更新版本。
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() 方法的方式。
由于瓶子不提供机制,因此需要破解。如果您使用默认的 WSGI 服务器,这可能是最干净的一个:
在 Bottle 的代码中,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
...
这是一个选项:提供自定义服务器(与默认值相同),它记录自己:
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)
这个同样笨拙的 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()
我想瓶子网络服务器永远运行,直到它终止。没有类似的方法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() if isinstance(app, basestring): app = load_app(app) if not callable(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
无论如何都没有通用的方法。
这与sepero和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。
我发现这个解决方案是最简单的,但它确实需要安装“psutil”包才能获得当前进程。它还需要“信号”模块,但这是标准库的一部分。
@route('/shutdown')
def shutdown():
current_process = psutil.Process()
current_process.send_signal(signal.CTRL_C_EVENT)
return 'Shutting down the web server'
希望这对某人有用!
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,我们可以通过内置模块来实现它:
这是服务器代码:
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 下进行了测试。
这个问题在我的谷歌搜索中排名第一,所以我将发布我的答案:
当使用 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 方法将停止服务器。