28

When starting a bottle webserver without a thread or a subprocess, there's no problem. To exit the bottle app -> CTRL + c.

In a thread, how can I programmatically stop the bottle web server ?

I didn't find a stop() method or something like that in the documentation. Is there a reason ?

Sandro Munda
  • 36,427
  • 21
  • 94
  • 117
  • @ThiefMaster (I cannot post on your deleted answer so I post here): why did you delete it? Is `sys.exit(0)` a bad solution? If so, why? I tried and indeed it does not work, but having your answer which explains *why* it does not work would be interesting :) – Basj May 05 '20 at 09:52

11 Answers11

14

For the default (WSGIRef) server, this is what I do (actually it is a cleaner approach of Vikram Pudi's suggestion):

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

When I want to stop the bottle application, from another thread, I do the following:

server.stop()
Basj
  • 29,668
  • 65
  • 241
  • 451
mike
  • 1,534
  • 1
  • 23
  • 31
  • 1
    I got problems using "server_close()" in stop method, indicating closed file handles. using "self.server.shutdown()" instead helped. – rocksportrocker Sep 05 '13 at 09:39
  • @rocksportrocker You are right, thanks for pointing this out. I updated the code. – mike Sep 05 '13 at 10:04
  • 1
    Using this method I got "port already in use" error when trying to run the thread again. Using `self.server.server_close()` after the line `self.server.shutdown()` solved this problem for me. – Hassan Nov 15 '18 at 14:24
  • After trying MANY options this is the only one that has worked for me. In my `Qt` interface I can open a server, close it, and re-open it without any hiccups. Though using `shutdown()` didn't work for me, I had to use `server_close()` +1! – Green Cell Jan 09 '19 at 05:57
  • `server.stop()` *"From another thread"* is really important. If it's the same thread it does not work (see route `/stop` in my edit). – Basj May 05 '20 at 11:42
10

I had trouble closing a bottle server from within a request as bottle seems to run requests in subprocesses.

I eventually found the solution was to do:

sys.stderr.close()

inside the request (that got passed up to the bottle server and axed it).

  • 1
    Does anyone can say if there's any problem with this answer? Why this is not the more upvoted answer? It's a one-liner solution and seems to work perfectly. – xilopaint Jan 22 '19 at 19:31
6

An updated version of mike's answer.

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

The class WSGIRefServer is entirely copied with only 1 line added to the run() method is added. Also add a simple shutdown() method. Unfortunately, this is necessary because of the way bottle creates the run() method.

Sepero
  • 3,751
  • 1
  • 24
  • 22
  • 1
    Tried this in my own project, and it shuts everything down, but I also wanted to restart it afterward... to reload some things. But when I do, the server seems to come back online, but is unresponsive. Any thoughts? – Adam Haile Sep 16 '15 at 13:24
3

You can make your thread a daemon by setting the daemon property to True before calling start.

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

A deamon thread will stop whenever the main thread that it is running in is killed or dies. The only problem is that you won't be able to make the thread run any code on exit and if the thread is in the process of doing something, it will be stopped immediately without being able to finish the method it is running.

There's no way in Python to actually explicitly stop a thread. If you want to have more control over being able to stop your server you should look into Python Processes from the multiprocesses module.

enrybo
  • 1,787
  • 1
  • 12
  • 20
2

Since bottle doesn't provide a mechanism, it requires a hack. This is perhaps the cleanest one if you are using the default WSGI server:

In bottle's code the WSGI server is started with:

srv.serve_forever()

If you have started bottle in its own thread, you can stop it using:

srv.shutdown()

To access the srv variable in your code, you need to edit the bottle source code and make it global. After changing the bottle code, it would look like:

srv = None #make srv global
class WSGIRefServer(ServerAdapter):
    def run(self, handler): # pragma: no cover
        global srv #make srv global
        ...
Vikram Pudi
  • 919
  • 9
  • 6
2

Here's one option: provide custom server (same as default), that records itself:

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)
Dima Tisnek
  • 9,367
  • 4
  • 48
  • 106
1

I suppose that the bottle webserver runs forever until it terminates. There are no methonds like stop().

But you can make something like this:

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

Then you can use Watcher.kill() when you need to kill your server.

Here is the code of run() function of the bottle:

try: app = app or 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')

As you can see there are no other way to get off the run loop, except some exceptions. The server.run function depends on the server you use, but there are no universal quit-method anyway.

Igor Chubin
  • 51,940
  • 8
  • 108
  • 128
1

This equally kludgy hack has the advantage that is doesn't have you copy-paste any code from 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()
blais
  • 570
  • 5
  • 9
1

This is exactly the same method than sepero's and mike's answer, but now much simpler with Bottle version 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)

Also related: https://github.com/bottlepy/bottle/issues/1229 and https://github.com/bottlepy/bottle/issues/1230.


Another example with a route http://localhost/stop to do the shutdown:

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: it requires at least Bottle 0.13dev.

Basj
  • 29,668
  • 65
  • 241
  • 451
  • The ```srv``` attribute has been present in ```bottle.WSGIRefServer``` since several years ago, I don't understand why Bottle 0.13 is not released still! – BeardOverflow Oct 20 '20 at 19:09
0

I've found this solution to be the easiest, but it does require that the "psutil" package is installed, to get the current process. It also requires the "signals" module, but that's part of the standard library.

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

Hope that's of use to someone!

Adrian Mc
  • 128
  • 9
-1

This question was top in my google search, so i will post my answer:

When the server is started with the Bottle() class, it has a method close() to stop the server. From the source code:

""" Close the application and all installed plugins. """

For example:

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

Calling stop method will stop de server.

Slye
  • 148
  • 1
  • 11
  • Interesting solution @Slye! Could you post a full example (with `import bottle`, `run(...)`, etc.) to make your code reproducible? Here we don't really know how to use your code... Thanks in advance! – Basj May 05 '20 at 09:42
  • I tried `app = Bottle()` etc. but then doing `app.close()` does not stop the server. – Basj May 05 '20 at 11:13